Skip to content

Use route-level config to set allowedRoles #134

@geoffrich

Description

@geoffrich

Related: #65, #60, #66, #102

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 the pattern 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions