Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate Static Web App Auth + SvelteKit #102

Open
2 tasks done
geoffrich opened this issue Jan 20, 2023 · 11 comments · May be fixed by #149
Open
2 tasks done

Investigate Static Web App Auth + SvelteKit #102

geoffrich opened this issue Jan 20, 2023 · 11 comments · May be fixed by #149
Milestone

Comments

@geoffrich
Copy link
Owner

geoffrich commented Jan 20, 2023

Azure Static Web Apps provides built-in auth features. However, I'm not sure how well they play with SvelteKit, especially during development.

We should investigate the following and document anything the adapter cannot support. It would also be good to integrate with a demo app.

  • protecting routes (either via the static web app config or through a SvelteKit hook)
  • login/logout
  • displaying information about current auth status (e.g. username)
  • how this works when running locally

Possibly to consider: a custom handle hook provided by the adapter that mocks out or otherwise forwards auth requests to the SWA emulator. edit: this part not needed, see #96 (comment)

Relevant docs:

Additional TODOs

@tlaundal
Copy link
Contributor

tlaundal commented Jan 22, 2023

I've successfully been using auth in SWA with SvelteKit.

I did a short writeup now: https://tt.weblog.lol/2023/01/protecting-pages-in-an-azure-static-web-app-built-with-sveltekit

I haven't looked too much into how to handle this in local development, I just have an environment variable that I use to control whether I am logged in or not. I wish there was an easy way to run the SWA locally with both frontend and backend, but I couldn't get that to work.

I was intending to perhaps make a PR here for the code that parses the auth header, so it can be injected automatically by the adapter, instead of needing to add a hook.

@geoffrich
Copy link
Owner Author

geoffrich commented Jan 23, 2023

Ah, thanks so much for writing that! It fills in some of the blanks from my experimentation this weekend.

The most puzzling thing has to do with the x-ms-client-principal header not being set all the time. I was testing with a +server.js route that just returns the headers it was passed. Something like this:

import { json } from "@sveltejs/kit";

/** @type {import('./$types').RequestHandler} */
export const GET = async (event) => {
  return json({
    headers: Object.fromEntries(event.request.headers)
  });
};

I was super confused why this wasn't returning the client principal header (though I was getting x-ms-auth-token, x-ms-client-principal-id, and x-ms-client-principal-idp). Your comment cleared things up:

Beware though; the header is only set on protected routes, and not on routes where anonymous is one of the roles, even if the user is authenticated! I spent a while debugging this. I think the best approach is to never set anonymous as an allowedRoles option.

If I specify allowedRoles: ["authenticated"] in the routes config, I do get back the client principal header.

However, I think the underlying behavior is a bug in SWA. I saw in a SWA GitHub issue thread somewhere that only /api requests will include the client principal header. I think this is (hopefully) a bug that happens because we're rewriting requests to /api/__render instead of hitting the /api route directly -- we should have that header all the time because /api/__render is an API route. I'll likely open an issue on the SWA repo to confirm.

EDIT: opened the issue. It seems to be an issue with routes that use navigationFallback and do not have an allowedRole. I was only able to repro on the deployed site, though; it seemed to work when running locally. Azure/static-web-apps#1053

To see the headers passed to /api/__render, I updated my hooks.server.js to return the headers when the request includes "/api" (otherwise, it would return a SvelteKit 404 page). In this case, I also get back the client principal header when I request "/api/__render", even though no allowedRoles were specified for that route.

import { json } from "@sveltejs/kit";

/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
  if (event.request.url.includes("/api")) {
    return json({
      headers: Object.fromEntries(event.request.headers)
    });
  }
  const response = await resolve(event);
  return response;
}

While it's nice that it works if you lock down routes via the config, I'd also like it to be possible to login with SWA and handle all route protection in SvelteKit directly, even when no allowedRoles are specified.

Other awkward things with using the routes config to block access to certain paths:

  • when running with the SWA CLI (via a method I'll document in Investigate improving SWA CLI story #96), the wildcard at the end of a path doesn't work as expected. For instance, if I add allowedRoles to "/sverdle*", all requests to paths starting with /sverdle should be blocked. In actuality, only "/sverdle" is blocked, meaning the "/sverdle/__data.json" request succeeds locally even though it doesn't succeed in production. This makes it possible to navigate client-side to certain pages in development that are blocked when running in production.
  • when routes are successfully blocked in production when navigating client-side, a SvelteKit "internal error" page is shown (because it received a 401 when requesting data). I still need to investigate if/how it's possible to customize this to show a better message or (ideally) redirect to a login page.
  • [security concern] even if the above are resolved, if you attempt to restrict access to a page via the routes config and that page does not need to load data, you could still reach that page via client-side navigation (since there aren't any requests for SWA to block). This may be something we try to warn/error on at build time.
  • [security concern 2] I want to make sure it's not possible to spoof the x-ms-original-url header. We are using that to determine which SvelteKit URL to route to, so if you can hit /api/__render spoof it, then you could theoretically access pages that SWA would block. I think it's probably okay, but want to double check

One possible solution to these issues is to get the client principal header coming back consistently so that you can handle all auth in SvelteKit instead of the routes config (though ideally we could support both options).

I haven't looked too much into how to handle this in local development, I just have an environment variable that I use to control whether I am logged in or not. I wish there was an easy way to run the SWA locally with both frontend and backend, but I couldn't get that to work.

I have some ideas for local development that I'll add over on #96.

I was intending to perhaps make a PR here for the code that parses the auth header, so it can be injected automatically by the adapter, instead of needing to add a hook.

PRs welcome! I've opened #103 to track that work and add some pointers. No worries if you can't get to it, I'll likely get to it at some point.

@geoffrich
Copy link
Owner Author

Oh, another awkward thing: if you add allowedRoles to a route that has non-GET methods, you need to also add rewrite: "/api/__render" so it gets passed along to the underlying Azure function. Otherwise you'll get an error when you attempt to POST or whatever.

{
  route: "/sverdle",
  allowedRoles: ["authenticated"],
  rewrite: "/api/__render"
}

Not sure if there's a way to make this nicer. Maybe we'd preprocess the routes to add this, but is there a case where you wouldn't want to rewrite to the Azure function?

@tlaundal
Copy link
Contributor

You've discovered a lot of stuff I didn't encounter when just trying to make my app work.

I agree it seems likely that most of these can be solved if the SWA bug for sending the client principal header is solved. However, I think handling all auth in SvelteKit is a no-go, because pre-rendered pages would not hit the /api/__render function.

I'll give #103 a go tonight.

@thomasgauvin
Copy link

thomasgauvin commented Jan 24, 2023

Reading this thread to gather more context for #102

I've successfully been using auth in SWA with SvelteKit.

I did a short writeup now: https://tt.weblog.lol/2023/01/protecting-pages-in-an-azure-static-web-app-built-with-sveltekit

I haven't looked too much into how to handle this in local development, I just have an environment variable that I use to control whether I am logged in or not. I wish there was an easy way to run the SWA locally with both frontend and backend, but I couldn't get that to work.

I was intending to perhaps make a PR here for the code that parses the auth header, so it can be injected automatically by the adapter, instead of needing to add a hook.

@tlaundal I've been developing a SWA + Svelte project recently, I was able to get frontend & backend working by passing the Svelte address as the API dev server url:

swa start http://127.0.0.1:5173 --api-devserver-url http://127.0.0.1:5173

It's been working for APIs (I'm getting the x-ms-client-principal in my +server.ts), I haven't been using auth headers for SSR or rewriting yet though, I can try it out

@tlaundal
Copy link
Contributor

tlaundal commented Jan 24, 2023

@tlaundal I've been developing a SWA + Svelte project recently, I was able to get frontend & backend working by passing the Svelte address as the API dev server url:

swa start http://127.0.0.1:5173 --api-devserver-url http://127.0.0.1:5173

It's been working for APIs (I'm getting the x-ms-client-principal in my +server.ts), I haven't been using auth headers for SSR or rewriting yet though, I can try it out

From a quick test this seems to work perfectly! It gives a really nice flow for testing logout and login.
Perhaps create a PR where this is added to the README, @thomasgauvin?

It would be nice to have a way for swa to actually serve the static files and run the function emulator, though. I ran into a case last week where something was working in npm run dev (and also preview I think), but not in Azure Functions. Fortunately I was able to get swa to start the backend at least (couldn't get backend and frontend to work at the same time), and this was enough to debug the problem. This probably belongs in #96, though.

@geoffrich
Copy link
Owner Author

I think the folks on this thread have already seen it, but I'm tracking SWA CLI config improvements in #96. Looks like we landed on something similar: #96 (comment).

@geoffrich
Copy link
Owner Author

geoffrich commented Jan 28, 2023

One thought I want to write down re: @tlaundal 's comment

However, I think handling all auth in SvelteKit is a no-go, because pre-rendered pages would not hit the /api/__render function.

I haven't tested this myself, but I'm wary about protecting prerendered pages via SWA config

  • the code to render the page will still be in the JS bundle delivered to the client (though it could be code-split into a separate bundle, this probably depends on the size of the page/site)
  • if you link to the protected page on a non-authed page, the user will be able to navigate to it via client-side routing (since this won't create a network request that SWA can block)

So IMO you shouldn't count on prerendered pages being completely inaccessible - a dedicated person could probably get to it if they really wanted to

@thomasgauvin
Copy link

thomasgauvin commented Mar 8, 2023

Reading this thread to gather more context for #102

I've successfully been using auth in SWA with SvelteKit.
I did a short writeup now: https://tt.weblog.lol/2023/01/protecting-pages-in-an-azure-static-web-app-built-with-sveltekit
I haven't looked too much into how to handle this in local development, I just have an environment variable that I use to control whether I am logged in or not. I wish there was an easy way to run the SWA locally with both frontend and backend, but I couldn't get that to work.
I was intending to perhaps make a PR here for the code that parses the auth header, so it can be injected automatically by the adapter, instead of needing to add a hook.

@tlaundal I've been developing a SWA + Svelte project recently, I was able to get frontend & backend working by passing the Svelte address as the API dev server url:

swa start http://127.0.0.1:5173 --api-devserver-url http://127.0.0.1:5173

It's been working for APIs (I'm getting the x-ms-client-principal in my +server.ts), I haven't been using auth headers for SSR or rewriting yet though, I can try it out

I realized that #89 prevents the use of a /api route, which is what I was relying on to claim the above (going to deploy made me realize that that is not possible). So I am currently stuck trying to get my auth working locally (injected by SWA CLI) for routes other than the /api route (like /rpc for instance, which is what I renamed my api routes to).

I'm also encountering Azure/static-web-apps#1053, which is still blocking my usage of /rpc (and trying to access clientPrincipal in there)

@geoffrich
Copy link
Owner Author

For those following this issue, I opened a somewhat-related issue on better supporting the allowedRoles config in apps built with this adapter: #134

@thomasgauvin
Copy link

Regarding Azure/static-web-apps#1053, that should now be fixed, please have a look and let me know if this allows you to achieve what you wanted with SvelteKit

@geoffrich geoffrich added this to the 1.0 milestone Aug 20, 2023
@geoffrich geoffrich linked a pull request Aug 20, 2023 that will close this issue
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants