Configure Azure Static Web Apps
You can define configuration for Azure Static Web Apps 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 describes how to configure 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, you can place the file in any subfolder within the folder set as the app_location
. Additionally, if there's a build step, you must ensure that the build step outputs the file to the root of the output_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 themethods
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.
Define 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.
|
methods |
No | All methods | Defines an array of request methods that 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.
|
redirect |
No | n/a | Defines the file or path redirect destination for a request. |
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.
|
allowedRoles |
No | anonymous | Defines an array of role names required to access a route.
|
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 |
Specify 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, and are only supported at the end of a path. 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 won't 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
Secure 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.
Restrict access to entire application
You often want to require authentication for every route in your application. To lock down your routes, 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 Microsoft Entra sign-in 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 go directly to URLs generated by client-side routing rules, a server-side fallback route is required to serve the appropriate HTML page. The fallback page is often designated as index.html for your client-side app.
Note
Route rules aren't applied on requests that trigger navigationFallback
.
You can define a fallback rule by adding a navigationFallback
section. The following example returns /index.html for all static file requests that don't 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/*"]
}
}
For example, with the following directory structure, the above navigation fallback rule would result in the outcomes detailed in the following table.
├── images
│ ├── logo.png
│ ├── headshot.jpg
│ └── screenshot.gif
│
├── css
│ └── global.css
│
├── about.html
└── 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 isn't 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 |
/about.html | The HTML page. | 200 |
Any other path outside the /images or /css folders that doesn't match the path to a deployed file. | 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
- Security policies
- Encoding settings
- Cross-origin resource sharing (CORS) configuration
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 |
|
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.
Select 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 |
- |
.NET 7.0 isolated | Windows | 4.x | dotnet-isolated:7.0 |
- |
.NET 8.0 isolated | Windows | 4.x | dotnet-isolated:8.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 |
- |
Node.js 18.x | Linux | 4.x | node:18 |
- |
Node.js 20.x (preview) | Linux | 4.x | node:20 |
- |
Python 3.8 | Linux | 4.x | python:3.8 |
- |
Python 3.9 | Linux | 4.x | python:3.9 |
- |
Python 3.10 | Linux | 4.x | python:3.10 |
- |
.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 8.0 as the API language runtime version in the csproj file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.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
. For more information about the number of allowed IP address blocks, see Quotas in Azure Static Web Apps.
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 don't 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
Default authentication providers don't require settings in the configuration file.
Custom authentication providers use the
auth
section of the settings file.
For details on how to restrict routes to authenticated users, see Securing routes with roles.
Disable cache for authenticated paths
If you set up manual integration with Azure Front Door, you might want to disable caching for your secured routes. With enterprise-grade edge enabled, caching is already disabled for your secured routes.
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 Content Delivery Network (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 sign-in.
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, a trailing slash URL refers to a directory on the web server, while a nontrailing 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 affect 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 performance.
The following normalization and redirect rules apply for each of the available configurations:
Always
When you're 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/ |
/privacy.html | The /privacy.html file | 301 |
/privacy/ |
Never
When setting trailingSlash
to never
, all requests ending in a trailing slash are redirected to a nontrailing 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 |
/privacy.html | The /privacy.html file | 301 |
/privacy |
Auto
When you set trailingSlash
to auto
, all requests to folders are redirected to a URL with a trailing slash. All requests to files are redirected to a nontrailing 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/ |
/privacy.html | The /privacy.html file | 301 |
/privacy |
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 |
/privacy.html | The /privacy.html file | 200 |
/privacy.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/x",
"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/x | Since the route rule disables X authorization, a 404 error is returned. This error then 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 nonexistent 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.