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

OAuth migration | Core OAuth authentication middleware #1223

Merged
merged 11 commits into from
Oct 26, 2023

Conversation

raphaelkabo
Copy link
Contributor

@raphaelkabo raphaelkabo commented Oct 12, 2023

This PR migrates the authentication middleware used by the MMA Express server from 'classic' (calling IDAPI's /auth/redirect endpoint on every request) to Okta-powered OAuth2.

Warning

The new OAuth mechanism this PR introduces is disabled by setting useOAuth: false in the okta-STAGE.json config file stored in S3. It should remain disabled until the upcoming 'server-side Okta auth' PR is merged in, because without that PR, if OAuth were enabled, we would be introducing a security vulnerability in accessing certain APIs. We want to merge this PR into main though, to prevent needing to keep it updated with main and to spot any hidden issues early in testing. While the useOAuth flag is set to false, the authentication behaviour will be identical to how it has always worked.

Changes

  • Added some new packages: @okta/jwt-verifier (to verify OAuth tokens), openid-client (to set up an OIDC client in MMA), and ms (because counting is hard and that's why we have computers).
  • The withIdentity authentication middleware now calls one of two functions: authenticateWithIdapi, which is the old middleware, or authenticateWithOAuth, which is the new middleware. This is controlled by a useOkta boolean in the Okta config file stored in S3 and retrieved by oktaConfig.ts.
    • See the table below for a comparison of the 'classic' middleware, the OAuth middleware, and the process we'll adopt after in-progress PRs are finished.
    • The OAuth middleware runs on each call MMA makes to its server, whether that's a frontend call or an API proxy request.
    • With the OAuth middleware, we're going for a complete replication of the behaviour of the previous Identity middleware - users shouldn't notice anything different, except that everyone will be logged out of MMA when we deploy this to PROD and enable useOAuth. (This is okay because MMA sessions are only valid for 30 minutes anyway).
    • When useOAuth is true, we're still dual-running this migration with the classic authorization behaviour for API endpoints handled by MMA. When useOAuth is true, users will authenticate via OAuth, but all API calls will still be authorized with the classic IDAPI cookies.
    • Future PRs will progressively migrate API calls until none of them use IDAPI cookies.
  • Added a handler for /oauth/callback, the callback route to which the browser returns after the user gets a valid session.
  • Added Jest tests to provide as much coverage as possible for the new middleware, with the proviso that at this stage, testing the OAuth flow end-to-end in Cypress would require a large time commitment to set Cypress up to run on a local domain so that cookies work, and would also require us to use Identity endpoints to generate test users, which may be deprecated in the next year (see the Cypress setup in Gateway for an example of this).

Huge thanks to @coldlink (I borrowed a lot of the OAuth code from his work in Gateway) and @pvighi, who both helped a lot with bringing this PR together.

Before This PR After server-side auth PR After full migration
Cookies SC_GU_U, SC_GU_LA, GU_U GU_ACCESS_TOKEN, GU_ID_TOKEN, GU_SO, SC_GU_U, SC_GU_LA, GU_U " - " GU_ACCESS_TOKEN,
GU_ID_TOKEN,
GU_SO,
GU_U
Session validity SC_GU_LA generated in the last 30 minutes withOAuth: false: SC_GU_LA generated in the last 30 minutes " - " OAuth tokens which have expiry of 30 minutes
withOAuth: true: OAuth tokens which have expiry of 30 minutes
Auth middleware Calls IDAPI to validate cookies withOAuth: false: Calls IDAPI to validate cookies withOAuth: true: After local validation, calls Okta to validate tokens for API-token-based API routes Locally validates OAuth tokens, calls Okta to validate tokens for API-token-based API routes
withOAuth: true: Locally validates OAuth tokens
OAuth-enabled API routes (IDAPI, AAPI, MDAPI) Are sent IDAPI cookies " - " " - " Are sent the OAuth access token
API token-based API routes (Lambdas, etc.) Are sent an admin-level API token Are sent an admin-level API token We call Okta to validate tokens, then are sent an admin-level API token " - " - "

A quick recap of the new authentication flow:

  • If a public route is being requested, we validate any existing OAuth tokens. If they're valid, they're passed to the frontend to render 'logged in' components. We don't do anything else here.
  • If we have a GU_SO cookie, we need to log the user out. We delete the access and ID token cookies and redirect the user to Gateway for authorization.
  • If we have valid access and ID tokens, the user is logged in. We set the frontend state, and let the request proceed.
  • If we don't have tokens, or they're invalid, we redirect the user to Gateway for authorization.
  • After the OAuth flow completes, the browser returns to /oauth/callback. Here we exchange the token we receive for access and ID tokens from Okta, and call IDAPI to give us a fresh set of classic IDAPI cookies.
  • Finally, we redirect the user to the page they were originally on. Simples!

Reviews and testing

It might be easiest to go through this PR commit-by-commit, which I've done my best to make easy to do. There are a couple of support commits, one large commit introduces the two parts of the OAuth middleware flow, and then a commit with a suite of Jest tests.

We'll test this by deploying it on CODE and running some manual tests, as noted in this testing plan. Then we'll do the same on PROD.

  • Tested on CODE
  • Tested on PROD

Remaining work

Related PRs

@raphaelkabo raphaelkabo marked this pull request as ready for review October 20, 2023 13:27
Copy link
Member

@coldlink coldlink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good so far from my side, a few changes required though i think

server/__tests__/oauth.test.ts Outdated Show resolved Hide resolved
server/oauth.ts Outdated Show resolved Hide resolved
server/oauth.ts Outdated Show resolved Hide resolved
server/oauth.ts Outdated Show resolved Hide resolved
server/oauth.ts Outdated Show resolved Hide resolved
We do not need to generate IDAPI cookies
by making a separate call to IDAPI as part
of the OAuth flow because Gateway already
generates these for us. However, in DEV,
we have been pointing MMA to CODE Gateway
during the OAuth flow, which means that the
cookies have been inaccessible from DEV
MMA. Changes:

- Remove the code to generate IDAPI cookies
- Point MMA DEV to Gateway DEV for OAuth
- Create a dev OpenID client which correctly
sets and checks the access/ID tokens, which
are set to validate against CODE Okta, despite
running in DEV.
Copy link
Member

@coldlink coldlink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amazing work!

@raphaelkabo raphaelkabo merged commit f0242f8 into main Oct 26, 2023
10 checks passed
@raphaelkabo raphaelkabo deleted the rk/oauth-middleware branch October 26, 2023 10:54
@prout-bot
Copy link
Collaborator

Overdue on PROD (merged by @raphaelkabo 15 minutes and 6 seconds ago) What's gone wrong?

@prout-bot
Copy link
Collaborator

Seen on PROD (merged by @raphaelkabo 1 hour, 16 minutes and 17 seconds ago) Please check your changes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants