-
Notifications
You must be signed in to change notification settings - Fork 38
Description
The problem: Azure SWA allows you to secure routes via setting allowedRoles
in the routes config. However, it is not easy to take advantage of this feature with this SvelteKit adapter.
- you can't set an allowed role for all routes, since the adapter prevents overriding the "*" route
- to set an allowed role for other routes that you also want handled by SvelteKit, you have to manually add a rewrite to the SSR function, which is technically private API
The proposal: use SvelteKit's route-level config to associate allowedRoles with a route. This could be extended for other route-level configuration in the future, if needed.
For example, if you have a route at /secured
, you could set allowedRoles
in the corresponding +page.js
or +page.server.js
file like so:
export const config = {
allowedRoles: ['admin']
}
The adapter would use this information to write the following route rule:
{
"route": "/secured",
"allowedRoles": ["admin"],
"rewrite": "/api/__render"
}
You could also export config
from a +layout.js
, and the adapter would write a route
with an allowed role for each route under that layout.
This solves the aforementioned issues:
- you can set an allowed role for all routes (Unable to set wildcard route #65) by exporting
config
from a root-level+layout.js
. - you no longer have to set
rewrite
yourself, and renaming your route folders in SvelteKit will automatically update the config (routes and config stay in sync)
It also has several more benefits
- we can automatically protect
__data.json
routes as well, which are generated by SvelteKit to fetch data
Passing routes
to the adapter would still be supported as it is today. Those routes would appear before any routes generated by the adapter, so to have priority.
Technical breakdown
During the adapter step, SvelteKit provides a routes
object representing all the routes in the app and the config associated with those routes. For example:
[
{
id: '/sverdle/how-to-play',
api: { methods: [] },
page: { methods: [Array] },
segments: [ [Object], [Object] ],
pattern: /^\/sverdle\/how-to-play\/?$/,
prerender: true,
methods: [ 'GET' ],
config: { foo: 'bar' }
},
{
id: '/[slug]/[anotherslug]',
api: { methods: [] },
page: { methods: [Array] },
segments: [ [Object], [Object] ],
pattern: /^\/([^/]+?)\/([^/]+?)\/?$/,
prerender: false,
methods: [ 'GET' ],
config: { foo: 'bar' }
}
]
The types for RouteDefinition are in the SvelteKit repo
We can convert the pattern
regex into the route
property needed by the SWA config. (We can't use route.id
since it could include layout groups or optional parameters). Then, for each route, write a rule with the route pattern and the specified allowedRoles
. We may also want to write a rule for the __data.json
endpoint associated with the route.
One sticking point is the relative inflexibility of Azure SWA route pattern. The wildcard can only appear at the end of the route pattern, so it wouldn't be possible to protect /blog/[slug]/edit
but not /blog/[slug]
- they would both be represented by the /blog/*
pattern. We would need to detect this conflict at build time and throw an error.
Also, because route config applies to an entire route folder, you couldn't protect an individual slug with this method - either you protect the entire /blog/[slug]
route, or none of it. You could get around this by passing custom routes to the adapter, but then you would have to handle the rewrite
property yourself.
We also may want to warn/error if a user-supplied route does not set allowedRoles
, since that will override the route generated by the adapter.
Limitations
There are some limitations:
- the SWA config file has a max size of 20KB. Apps with a large number of protected routes could hit this restriction, since we'll be writing an object for each unique route. There may be ways we can merge rules, e.g. if all routes under a given route segment are protected, then write a single wildcard rule
- as mentioned in the previous section, dynamic routes are limited to what can be expressed with SWA's wildcard syntax
- using this auth at dev time may be tricky (see Investigate improving SWA CLI story #96, Investigate Static Web App Auth + SvelteKit #102). Even with SWA CLI working, you will need to re-generate the config whenever you change the
allowedRoles
. - while you can protect static/prerendered pages with this method, they will still be accessible if you client-side route to them and may appear in the bundle. We'll want to document this carefully (see also Investigate Static Web App Auth + SvelteKit #102 (comment)). The SWA docs call out this limitation as well:
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.
Potential future enhancements
I wouldn't include these in the initial release of this feature, but here are some potential improvements:
- protecting individual methods (e.g. require admin role for POST but not GET). Though I suspect it would be better to enforce this at runtime in the app itself instead of via config.
- automatically transform custom route config passed to the adapter (e.g. to allow protecting individual slugs in the
/blog/[slug]
example above). One idea: if you pass a route handled by the SvelteKit app (determined using thepattern
property in the adapter) but don't rewrite or redirect it, automatically add the rewrite rule
Any questions?
Would love to hear feedback about this approach? Is this workable? Is there anything I missed?