Skip to content

Conversation

@pranav-super
Copy link
Contributor

This is the main pull request relating to OIDC support, although there are very small accompanying pull requests on Aerie (main) and Aerie-Gateway that go with this.

OIDC Overview

OIDC refers to the addition of authentication to OAuth2.0's authorization mechanisms. When it is used here and throughout this PR, it generally entails authenticating against an Identity Provider (IdP) and using the permissions that it provides to authorize what a given user can and cannot do.

While Aerie and Aerie-UI have existing authorization mechanisms, through Aerie-UI's robust permissions.ts and Aerie's Hasura metadata, which specifies permissions for each table, there isn't much support for authenticating users against an external IdP. Such authentication against an external, likely organizational IdP, is a fairly general need, and it is common for that IdP to use the OIDC framework. As such, the focus of this PR was to add this capability, specifically following the OAuth2.0 specification.

Details on the specifics of OIDC and OAuth2.0 are left out, but can be found here. OAuth2.0, in its specification, defines a variety of "authorization flows", where the end goal for a given user after submitting their credentials is to obtain an access token with which they can access the application. This PR introduces to Aerie-UI support for Authorization Code Flow with PKCE, which seems to be the most recommended and best supported flow for our given use case. That being said, it wouldn't be unimaginably difficult to support other authorization flows, although that was not the focus of this PR and that is generally an item for discussion, below.

How Do I Run This?

This PR introduces a few new pieces to the .env puzzle. Within aerie-ui, the following have been introduced:

  • PUBLIC_AUTH_OIDC_ENABLED: must be set to true to enable OIDC functionality.
  • OIDC_WELL_KNOWN_URL: the "well-known" URL endpoint your IdP. This usually ends or includes in the URL the string .well-known.
  • OIDC_AUTHORIZATION_URL: the endpoint of the IdP where the authorization code is obtained. Typically ends in /auth.
  • OIDC_TOKEN_URL: the endpoint of the IdP where the authorization code is obtained. Typically ends in /token.
  • OIDC_LOGOUT_URL: the endpoint of the IdP where the authorization code is obtained. Typically ends in /logout.
  • OIDC_JWKS_URL: the endpoint of the IdP where the authorization code is obtained. Typically ends in /certs.
  • OIDC_SCOPES: the "scopes" that the token should provide. These are usually details about the user and can depend on the specific IdP being used. They are space separated. Generally, defaulting to some superset of openid profile email, if not just that, is sufficient.
  • OIDC_CLIENT_ID: the ID in the IdP assigned to your client/application. This could be something like "aerie".
  • OIDC_CLIENT_SECRET: this is presently not used, but can be relevant future implementations if a flow other than PKCE is used.
  • OIDC_REDIRECT_URI: the URI to redirect to after obtaining the authorization code.
  • OIDC_AUDIENCE: the audience for the token. Typically matches OIDC_CLIENT_ID.
  • OIDC_ISSUER: the base URL of the IdP for your given "realm".

Finally, if using OIDC, you will likely also need to change your HASURA_GRAPHQL_JWT_SECRET in Aerie's .env to use RS256 as its type, and replace the key with a jwk_url matching the above configuration. If running Aerie-Gateway locally instead of with the rest of Aerie, this environment variable should be updated there as well.

Our Goal Here

Generally, our goal here is to be as minimally invasive as we can. While some pretty big changes are made, we tried to make sure too much wasn't altered. For example, all OIDC functionality is sectioned off to its own folder to ensure there is as little interference with existing auth functionality as possible.

There are also things we considered but ultimately descoped. Namely, phasing out the user cookie. While a review of the code suggesting that this cookie could logically be replaced with an access token, leading to a more uniform implementation overall, doing so would require slightly more sweeping changes than we would like to introduce in this PR at this moment. Doing so for the no-auth option ultimately seems to entail mirroring that implementation to what is done in the SSO/CAM implementation. Doing so would, however, require modification to Aerie-Gateway as well to support such functionality. As such, a cohesive fix for this should be pushed to a separate PR, especially considering that there already might be other things we would like to consider as far as refactoring the existing auth code and potentially integrating it or mirroring it more closely with the OIDC functionality being introduced here. This also implies that any refactor of the CAM/SSO flow is descoped in this PR.

What is Worth Knowing When Reviewing?

OIDC authentication in Aerie-UI does not rely as heavily on Aerie-Gateway as the other options do. It uses it briefly, in token/session validation, and as such required a small update to the gateway to support JWKs-based decoding. Otherwise, however, the UI operates largely independently of the gateway in performing authentication.

We also tried to reduce the reliance on redirects and silent logouts/errors, which were present in a lot of error cases. For example, if a JWT token expires, the current behavior was that as soon as it was caught in a Hasura request or in a Hasura subscription, the user would be logged out. This led to some very difficult-to-trace errors during development, and we thought it might be more intuitive to the user that, instead of immediately logging out, we make the errors more explicit and suggest that the user should log out rather than doing it for them.

Subscriptions have also changed fairly significantly. Prior to this PR, all references to the user were passed down using the Svelte PageData or LayoutData construct. While this makes perfect sense if the user is static and its token and other parts of that object are entirely static, it stops working very well when the token and the user object do change. Since our OIDC flow introduces the concept of refreshes, which mean that at the very least, the token assigned to a user can change, then something else becomes preferable. We make use of a user store instead to reflect these changes, and therefore all subscriptions, instead of having a user (or nothing) passed to them, now have the user obtained from the store. This change wasn't made, however, to the request framework, as there are queries (namely getUserQueries and getRolePermissions) that require a user to be passed to them when the user store will not yet have a value (as these are invoked in the server hooks). In doing this, a lot of uses of PageData were found to exist solely to propagate a static value of the user down to various components. As a store was being used instead, those uses were cleaned up, which led to the change and removal of a lot of +page.ts and +layout.ts files.

Discussion Items

A large unknown here is testing! Testing has proven to be quite difficult using typical frameworks for mock IdPs, like Dex (which doesn't seem to support users providing custom token refresh intervals, instead only allowing a default of 24 hours), and using a custom setup with a real IdP, like Keycloak, seems far too heavyweight. As such, comprehensive end-to-end testing is something that isn't yet included in this PR and we welcome any suggestions. We posted this PR without said tests just because there is a fair amount to review, so we didn't want to wait until testing was figured out to get any reviewing started, especially since if there are suggestions for additional sweeping changes, those tests would soon be made obsolete!

The idea was floated in development to introduce a (protected) folder to wrap all non-authentication-related routes. This would be purely organizational and wouldn't affect any URLs. It isn't included in this PR because it would make any future rebases extremely difficult, so that was left as a potential final change.

As mentioned before, we might want to consider supporting other authentication flows. While that is not in scope of this PR, we would like to suggest the intention here as a discussion item.

Finally, we mentioned earlier a potential refactor of the existing SSO/CAM and no-auth flows. While it isn't clear when we would do that and exactly what that would look like, we would like to make sure the interest in doing so is documented here. Such a change would also affect gateway somewhat significantly, so we would probably want to make a separate discussion post and meet about it following this PR!

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6 Security Hotspots

See analysis details on SonarQube Cloud

psubram3 and others added 12 commits October 10, 2025 17:54
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
…instead of manually setting headers, like what is done in refresh

Co-authored-by: Pranav Subramanian <[email protected]>
Co-authored-by: Jonathan Morton <[email protected]>
unnecessary if using SSR, as we need to await parent anyways before calling userStore, which isn't particularly helpful on browser here anyways in the case of SSR...so easier to just remove.
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 this pull request may close these issues.

3 participants