Configure Azure Static Web Apps

Configuration for Azure Static Web Apps is defined in the staticwebapp.config.json file, which controls the following settings:

  • Routing
  • Authentication
  • Authorization
  • Fallback rules
  • HTTP response overrides
  • Global HTTP header definitions
  • Custom MIME types
  • Networking

Note

routes.json that was previously used to configure routing is deprecated. Use staticwebapp.config.json as described in this article to configure routing and other settings for your static web app.

This document is regarding Azure Static Web Apps, which is a standalone product and separate from the static website hosting feature of Azure Storage.

File location

The recommended location for the staticwebapp.config.json is in the folder set as the app_location in the workflow file. However, the file may be placed in any subfolder within the folder set as the app_location.

See the example configuration file for details.

Important

The deprecated routes.json file is ignored if a staticwebapp.config.json exists.

Routes

You can define rules for one or more routes in your static web app. Route rules allow you to restrict access to users in specific roles or perform actions such as redirect or rewrite. Routes are defined as an array of routing rules. See the example configuration file for usage examples.

  • Rules are defined in the routes array, even if you only have one route.
  • Rules are evaluated in the order as they appear in the routes array.
  • Rule evaluation stops at the first match. A match occurs when the route property and a value in the methods array (if specified) match the request. Each request can match at most one rule.

The routing concerns significantly overlap with authentication (identifying the user) and authorization (assigning abilities to the user) concepts. Make sure to read the authentication and authorization guide along with this article.

Defining routes

Each rule is composed of a route pattern, along with one or more of the optional rule properties. Route rules are defined in the routes array. See the example configuration file for usage examples.

Important

Only the route and methods (if specified) properties are used to determine whether a rule matches a request.

Rule property Required Default value Comment
route Yes n/a The route pattern requested by the caller.
  • Wildcards are supported at the end of route paths.
    • For instance, the route /admin* matches any route beginning with /admin.
methods No All methods Defines an array of request methods which match a route. Available methods include: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, and PATCH.
rewrite No n/a Defines the file or path returned from the request.
  • Is mutually exclusive to a redirect rule.
  • Rewrite rules don't change the browser's location.
  • Values must be relative to the root of the app.
redirect No n/a Defines the file or path redirect destination for a request.
  • Is mutually exclusive to a rewrite rule.
  • Redirect rules change the browser's location.
  • Default response code is a 302 (temporary redirect), but you can override with a 301 (permanent redirect).
statusCode No 301 or 302 for redirects The HTTP status code of the response.
headers No n/a Set of HTTP headers added to the response.
  • Route-specific headers override globalHeaders when the route-specific header is the same as the global header is in the response.
  • To remove a header, set the value to an empty string.
allowedRoles No anonymous Defines an array of role names required to access a route.
  • Valid characters include a-z, A-Z, 0-9, and _.
  • The built-in role, anonymous, applies to all users.
  • The built-in role, authenticated, applies to any logged-in user.
  • Users must belong to at least one role.
  • Roles are matched on an OR basis.
    • If a user is in any of the listed roles, then access is granted.
  • Individual users are associated to roles through invitations.

Each property has a specific purpose in the request/response pipeline.

Purpose Properties
Match routes route, methods
Process after a rule is matched and authorized rewrite (modifies request)

redirect, headers, statusCode (modifies response)
Authorize after a route is matched allowedRoles

Specifying route patterns

The route property can be an exact route or a wildcard pattern.

Exact route

To define an exact route, place the full path of the file in the route property.

{
  "route": "/profile/index.html",
  "allowedRoles": ["authenticated"]
}

This rule matches requests for the file /profile/index.html. Because index.html is the default file, the rule also matches requests for the folder (/profile or /profile/).

Important

If you use a folder path (/profile or /profile/) in the route property, it won't match requests for the file /profile/index.html. When protecting a route that serves a file, always use the full path of the file such as /profile/index.html.

Wildcard pattern

Wildcard rules match all requests in a route pattern, are only supported at the end of a path, and may be filtered by file extension. See the example configuration file for usage examples.

For instance, to implement routes for a calendar application, you can rewrite all URLs that fall under the calendar route to serve a single file.

{
  "route": "/calendar*",
  "rewrite": "/calendar.html"
}

The calendar.html file can then use client-side routing to serve a different view for URL variations like /calendar/january/1, /calendar/2020, and /calendar/overview.

Note

A route pattern of /calendar/* matches all requests under the /calendar/ path. However, it will not match requests for the paths /calendar or /calendar.html. Use /calendar* to match all requests that begin with /calendar.

You can filter wildcard matches by file extension. For instance, if you wanted to add a rule that only matches HTML files in a given path you could create the following rule:

{
  "route": "/articles/*.html",
  "headers": {
    "Cache-Control": "public, max-age=604800, immutable"
  }
}

To filter on multiple file extensions, you include the options in curly braces, as shown in this example:

{
  "route": "/images/thumbnails/*.{png,jpg,gif}",
  "headers": {
    "Cache-Control": "public, max-age=604800, immutable"
  }
}

Common uses cases for wildcard routes include:

  • Serving a specific file for an entire path pattern
  • Enforcing authentication and authorization rules
  • Implementing specialized caching rules

Securing routes with roles

Routes are secured by adding one or more role names into a rule's allowedRoles array. See the example configuration file for usage examples.

Important

Routing rules can only secure HTTP requests to routes that are served from Static Web Apps. Many front-end frameworks use client-side routing that modifies routes in the browser without issuing requests to Static Web Apps. Routing rules don't secure client-side routes. Clients should call HTTP APIs to retrieve sensitive data. Ensure APIs validate a user's identity before returning data.

By default, every user belongs to the built-in anonymous role, and all logged-in users are members of the authenticated role. Optionally, users are associated to custom roles via invitations.

For instance, to restrict a route to only authenticated users, add the built-in authenticated role to the allowedRoles array.

{
  "route": "/profile*",
  "allowedRoles": ["authenticated"]
}

You can create new roles as needed in the allowedRoles array. To restrict a route to only administrators, you could define your own role named administrator, in the allowedRoles array.

{
  "route": "/admin*",
  "allowedRoles": ["administrator"]
}
  • You have full control over role names; there's no list to which your roles must adhere.
  • Individual users are associated to roles through invitations.

Important

When securing content, specify exact files when possible. If you have many files to secure, use wildcards after a shared prefix. For example: /profile* secures all possible routes that start with /profile, including /profile.

Restricting access to entire application

It's common to require authentication for every route in an application. To enable this, add a rule that matches all routes and include the built-in authenticated role in the allowedRoles array.

The following example configuration blocks anonymous access and redirects all unauthenticated users to the Azure Active Directory login page.

{
  "routes": [
    {
      "route": "/*",
      "allowedRoles": ["authenticated"]
    }
  ],
  "responseOverrides": {
    "401": {
      "statusCode": 302,
      "redirect": "/.auth/login/aad"
    }
  }
}

Note

By default, all pre-configured identity providers are enabled. To block an authentication provider, see Authentication and authorization.

Fallback routes

Single Page Applications often rely on client-side routing. These client-side routing rules update the browser's window location without making requests back to the server. If you refresh the page, or navigate directly to URLs generated by client-side routing rules, a server-side fallback route is required to serve the appropriate HTML page (which is generally the index.html for your client-side app).

You can define a fallback rule by adding a navigationFallback section. The following example returns /index.html for all static file requests that do not match a deployed file.

{
  "navigationFallback": {
    "rewrite": "/index.html"
  }
}

You can control which requests return the fallback file by defining a filter. In the following example, requests for certain routes in the /images folder and all files in the /css folder are excluded from returning the fallback file.

{
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": ["/images/*.{png,jpg,gif}", "/css/*"]
  }
}

The example file structure below, the following outcomes are possible with this rule.

├── images
│   ├── logo.png
│   ├── headshot.jpg
│   └── screenshot.gif
│
├── css
│   └── global.css
│
└── index.html
Requests to... returns... with the status...
/about/ The /index.html file 200
/images/logo.png The image file 200
/images/icon.svg The /index.html file - since the svg file extension is not listed in the /images/*.{png,jpg,gif} filter 200
/images/unknown.png File not found error 404
/css/unknown.css File not found error 404
/css/global.css The stylesheet file 200
Any other file outside the /images or /css folders The /index.html file 200

Important

If you are migrating from the deprecated routes.json file, do not include the legacy fallback route ("route": "/*") in the routing rules.

Global headers

The globalHeaders section provides a set of HTTP headers applied to each response, unless overridden by a route header rule, otherwise the union of both the headers from the route and the global headers is returned.

See the example configuration file for usage examples.

To remove a header, set the value to an empty string ("").

Some common use cases for global headers include:

  • Custom caching rules
  • Enforcing security policies
  • Encoding settings
  • Configuring cross-origin resource sharing (CORS)

The following example implements a custom CORS configuration.

{
  "globalHeaders": {
    "Access-Control-Allow-Origin": "https://example.com",
    "Access-Control-Allow-Methods": "POST, GET, OPTIONS"
  }
}

Note

Global headers do not affect API responses. Headers in API responses are preserved and returned to the client.

Response overrides

The responseOverrides section provides an opportunity to define a custom response when the server would otherwise return an error code. See the example configuration file for usage examples.

The following HTTP codes are available to override:

Status Code Meaning Possible cause
400 Bad request Invalid invitation link
401 Unauthorized Request to restricted pages while unauthenticated
403 Forbidden
  • User is logged in but doesn't have the roles required to view the page.
  • User is logged in but the runtime cannot get the user details from their identity claims.
  • There are too many users logged in to the site with custom roles, therefore the runtime can't log in the user.
404 Not found File not found

The following example configuration demonstrates how to override an error code.

{
  "responseOverrides": {
    "400": {
      "rewrite": "/invalid-invitation-error.html"
    },
    "401": {
      "statusCode": 302,
      "redirect": "/login"
    },
    "403": {
      "rewrite": "/custom-forbidden-page.html"
    },
    "404": {
      "rewrite": "/custom-404.html"
    }
  }
}

Platform

The platform section controls platform specific settings, such as the API language runtime version.

Selecting the API language runtime version

To configure the API language runtime version, set the apiRuntime property in the platform section to one of the following supported values.

Language runtime version Operating system Azure Functions version apiRuntime value End of support date
.NET Core 3.1 Windows 3.x dotnet:3.1 December 3, 2022
.NET 6.0 in-process Windows 4.x dotnet:6.0 -
.NET 6.0 isolated Windows 4.x dotnet-isolated:6.0 -
Node.js 12.x Linux 3.x node:12 December 3, 2022
Node.js 14.x Linux 4.x node:14 -
Node.js 16.x Linux 4.x node:16 -
Python 3.8 Linux 3.x python:3.8 -
Python 3.9 Linux 4.x python:3.9 -

.NET

To change the runtime version in a .NET app, change the TargetFramework value in the csproj file. While optional, if you set a apiRuntime value in the staticwebapp.config.json file, make sure the value matches what you define in the csproj file.

The following example demonstrates how to update the TargetFramework element for NET 6.0 as the API language runtime version in the csproj file.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    ...
  </PropertyGroup>
...

Node.js

The following example configuration demonstrates how to use the apiRuntime property to select Node.js 16 as the API language runtime version in the staticwebapp.config..json file.

{
  ...
  "platform": {
    "apiRuntime": "node:16"
  }
  ...
}

Python

The following example configuration demonstrates how to use the apiRuntime property to select Python 3.8 as the API language runtime version in the staticwebapp.config..json file.

{
  ...
  "platform": {
    "apiRuntime": "python:3.8"
  }
  ...
}

Networking

The networking section controls the network configuration of your static web app. To restrict access to your app, specify a list of allowed IP address blocks in allowedIpRanges.

Note

Networking configuration is only available in the Azure Static Web Apps Standard plan.

Define each IPv4 address block in Classless Inter-Domain Routing (CIDR) notation. To learn more about CIDR notation, see Classless Inter-Domain Routing. Each IPv4 address block can denote either a public or private address space. If you only want to allow access from a single IP Address, you can use the /32 CIDR block.

{
  "networking": {
    "allowedIpRanges": [
      "10.0.0.0/24",
      "100.0.0.0/32",
      "192.168.100.0/22"
    ]
  }
}

When one or more IP address blocks are specified, requests originating from IP addresses that do not match a value in allowedIpRanges are denied access.

In addition to IP address blocks, you can also specify service tags in the allowedIpRanges array to restrict traffic to certain Azure services.

"networking": {
  "allowedIpRanges": ["AzureFrontDoor.Backend"]
}

Authentication

For details on how to restrict routes to authenticated users, see Securing routes with roles.

Disabling cache for authenticated paths

If you set up manual integration with Azure Front Door, you may want to disable caching for your secured routes. If you have enabled enterprise-grade edge this is already configured for you.

To disable Azure Front Door caching for secured routes, add "Cache-Control": "no-store" to the route header definition.

For example:

{
    "route": "/members",
    "allowedRoles": ["authenticated, members"],
    "headers": {
        "Cache-Control": "no-store"
    }
}

Forwarding gateway

The forwardingGateway section configures how a static web app is accessed from a forwarding gateway such as a CDN or Azure Front Door.

Note

Forwarding gateway configuration is only available in the Azure Static Web Apps Standard plan.

Allowed Forwarded Hosts

The allowedForwardedHosts list specifies which hostnames to accept in the X-Forwarded-Host header. If a matching domain is in the list, Static Web Apps uses the X-Forwarded-Host value when constructing redirect URLs, such as after a successful login.

For Static Web Apps to function correctly behind a forwarding gateway, the request from the gateway must include the correct hostname in the X-Forwarded-Host header and the same hostname must be listed in allowedForwardedHosts.

"forwardingGateway": {
  "allowedForwardedHosts": [
    "example.org",
    "www.example.org",
    "staging.example.org"
  ]
}

If the X-Forwarded-Host header doesn't match a value in the list, the requests still succeed, but the header isn't used in the response.

Required headers

Required headers are HTTP headers that must be sent with each request to your site. One use of required headers is to deny access to a site unless all of the required headers are present in each request.

For example, the following configuration shows how you can add a unique identifier for Azure Front Door that limits access to your site from a specific Azure Front Door instance. See the Configure Azure Front Door tutorial for full details.

"forwardingGateway": {
  "requiredHeaders": {
    "X-Azure-FDID" : "692a448c-2b5d-4e4d-9fcc-2bc4a6e2335f"
  }
}
  • Key/value pairs can be any set of arbitrary strings
  • Keys are case insensitive
  • Values are case-sensitive

Trailing slash

A trailing slash is the / at the end of a URL. Conventionally, trailing slash URL refers to a directory on the web server, while a non-trailing slash indicates a file.

Search engines treat the two URLs separately, regardless of whether it's a file or a directory. When the same content is rendered at both of these URLs, your website serves duplicate content which can negatively impact search engine optimization (SEO). When explicitly configured, Static Web Apps applies a set of URL normalization and redirect rules that help improve your website’s performance and SEO.

The following normalization and redirect rules will apply for each of the available configurations:

Always

When setting trailingSlash to always, all requests that don't include a trailing slash are redirected to a trailing slash URL. For example, /contact is redirected to /contact/.

"trailingSlash": "always"
Requests to... returns... with the status... and path...
/about The /about/index.html file 301 /about/
/about/ The /about/index.html file 200 /about/
/about/index.html The /about/index.html file 301 /about/
/contact The /contact.html file 301 /contact/
/contact/ The /contact.html file 200 /contact/
/contact.html The /contact.html file 301 /contact/

Never

When setting trailingSlash to never, all requests ending in a trailing slash are redirected to a non-trailing slash URL. For example, /contact/ is redirected to /contact.

"trailingSlash": "never"
Requests to... returns... with the status... and path...
/about The /about/index.html file 200 /about
/about/ The /about/index.html file 301 /about
/about/index.html The /about/index.html file 301 /about
/contact The /contact.html file 200 /contact
/contact/ The /contact.html file 301 /contact
/contact.html The /contact.html file 301 /contact

Auto

When setting trailingSlash to auto, all requests to folders are redirected to a URL with a trailing slash. All requests to files are redirected to a non-trailing slash URL.

"trailingSlash": "auto"
Requests to... returns... with the status... and path...
/about The /about/index.html file 301 /about/
/about/ The /about/index.html file 200 /about/
/about/index.html The /about/index.html file 301 /about/
/contact The /contact.html file 200 /contact
/contact/ The /contact.html file 301 /contact
/contact.html The /contact.html file 301 /contact

For optimal website performance, configure a trailing slash strategy using one of the always, never or auto modes.

By default, when the trailingSlash configuration is omitted, Static Web Apps applies the following rules:

Requests to... returns... with the status... and path...
/about The /about/index.html file 200 /about
/about/ The /about/index.html file 200 /about/
/about/index.html The /about/index.html file 200 /about/index.html
/contact The /contact.html file 200 /contact
/contact/ The /contact.html file 301 /contact
/contact.html The /contact.html file 200 /contact.html

Example configuration file

{
  "trailingSlash": "auto",
  "routes": [
    {
      "route": "/profile*",
      "allowedRoles": ["authenticated"]
    },
    {
      "route": "/admin/index.html",
      "allowedRoles": ["administrator"]
    },
    {
      "route": "/images/*",
      "headers": {
        "cache-control": "must-revalidate, max-age=15770000"
      }
    },
    {
      "route": "/api/*",
      "methods": ["GET"],
      "allowedRoles": ["registeredusers"]
    },
    {
      "route": "/api/*",
      "methods": ["PUT", "POST", "PATCH", "DELETE"],
      "allowedRoles": ["administrator"]
    },
    {
      "route": "/api/*",
      "allowedRoles": ["authenticated"]
    },
    {
      "route": "/customers/contoso*",
      "allowedRoles": ["administrator", "customers_contoso"]
    },
    {
      "route": "/login",
      "rewrite": "/.auth/login/github"
    },
    {
      "route": "/.auth/login/twitter",
      "statusCode": 404
    },
    {
      "route": "/logout",
      "redirect": "/.auth/logout"
    },
    {
      "route": "/calendar*",
      "rewrite": "/calendar.html"
    },
    {
      "route": "/specials",
      "redirect": "/deals",
      "statusCode": 301
    }
  ],
  "navigationFallback": {
    "rewrite": "index.html",
    "exclude": ["/images/*.{png,jpg,gif}", "/css/*"]
  },
  "responseOverrides": {
    "400": {
      "rewrite": "/invalid-invitation-error.html"
    },
    "401": {
      "redirect": "/login",
      "statusCode": 302
    },
    "403": {
      "rewrite": "/custom-forbidden-page.html"
    },
    "404": {
      "rewrite": "/404.html"
    }
  },
  "globalHeaders": {
    "content-security-policy": "default-src https: 'unsafe-eval' 'unsafe-inline'; object-src 'none'"
  },
  "mimeTypes": {
    ".json": "text/json"
  }
}

Based on the above configuration, review the following scenarios.

Requests to... results in...
/profile Authenticated users are served the /profile/index.html file. Unauthenticated users are redirected to /login by the 401 response override rule.
/admin, /admin/, or /admin/index.html Authenticated users in the administrator role are served the /admin/index.html file. Authenticated users not in the administrator role are served a 403 error1. Unauthenticated users are redirected to /login
/images/logo.png Serves the image with a custom cache rule where the max age is a little over 182 days (15,770,000 seconds).
/api/admin GET requests from authenticated users in the registeredusers role are sent to the API. Authenticated users not in the registeredusers role and unauthenticated users are served a 401 error.

POST, PUT, PATCH, and DELETE requests from authenticated users in the administrator role are sent to the API. Authenticated users not in the administrator role and unauthenticated users are served a 401 error.
/customers/contoso Authenticated users who belong to either the administrator or customers_contoso roles are served the /customers/contoso/index.html file. Authenticated users not in the administrator or customers_contoso roles are served a 403 error1. Unauthenticated users are redirected to /login.
/login Unauthenticated users are challenged to authenticate with GitHub.
/.auth/login/twitter As authorization with Twitter is disabled by the route rule, 404 error is returned, which falls back to serving /index.html with a 200 status code.
/logout Users are logged out of any authentication provider.
/calendar/2021/01 The browser is served the /calendar.html file.
/specials The browser is permanently redirected to /deals.
/data.json The file served with the text/json MIME type.
/about, or any folder that matches client side routing patterns The /index.html file is served with a 200 status code.
An non-existent file in the /images/ folder A 404 error.

1 You can provide a custom error page by using a response override rule.

Restrictions

The following restrictions exist for the staticwebapp.config.json file.

  • Max file size is 20 KB
  • Max of 50 distinct roles

See the Quotas article for general restrictions and limitations.

Next steps