Search code examples
spring-bootspring-securityoauth-2.0okta

Is a Secure Oauth2 Web App Flow with FE and BE the right approach?


We have a Web Application that mints an internal JWT used for authentication along with a secure cookie. As the organization has been moving to Okta there is an ask to integrate that login flow into our app.

Our current workflow:

  • We have a Spring Boot (version 2.5.x) login endpoint which receives a userId and Password.
  • The login is validated and then an internal jwt is created with the userId, assigned role and some other metadata.
  • The FE client receives that jwt and sends it for every authenticated request.

It poses a lot of challenges. After some analysis, I think I see how I want to approach it:

  • The Okta application is configured for "Grant Type". redirect and logout callbacks point to the FE client URL
  • The FE client makes the call to the Okta server to get the authorizationCode
  • The FE client makes a call to the BE server to do an /sso-login, passing in the authorizationCode as a parameter
  • the BE calls the okta {okta-server}/token endpoint to get the okta bearer token and parse the results to get userName
  • the internal JWT is minted based on the userName and associated roles and sent back to the FE along with a secure cookie

Does this flow make sense? Is it safe? Most literature and examples I see tend to involve SPA or integrate everything in the BE.

Because of the opaqueness of the Spring oauth2 integration I am favoring avoiding that for now and manually constructing my POST call to the /token endpoint.


Solution

  • According to latest recommendations, OAuth2 clients (applications to which tokens are delivered and which store those tokens) should be "confidential" (secured with some sort of secret). This requires it to run on a server you trust (neither Javascript based apps in a browser nor mobile apps can keep a secret).

    This means that, to follow OAuth2 best practices, your React app should not be an OAuth2 client nor have access to tokens. Its requests to the backend should be authorized with session cookies (which requires protection against CSRF attacks).

    A noticeable exception being Next.js apps using the NextAuth lib which handles authorization code and tokens storage server side (node) with a confidential client, and use session cookies for communication between the browser and server parts of the app.

    The solution I detail in this Baeldung article, is to configure a spring-cloud-gateway instance as an OAuth2 BFF (runs authorization code and refresh token flows, stores tokens, and bridges between cookie-based authorization (requests from the frontend) and Bearer-based authorization (requests forwarded to downstream resource servers). This is the exact same pattern as what NextAuth does for Next.js, but probably more scalable and easier to instrument with observability (also works with any frontend framework, not just Next.js).

    When user authentication is required:

    • the Javascript based app exits by setting the window location to an URI on the gateway to initiate authorization_code flow
    • the gateway redirects to the authorization server (Okta in your case)
    • after the user authenticated, the authorization server redirects back to the gateway with authorization-code
    • the gateway exchanges the authorization-code for tokens and store it in session
    • the gateway redirects back to the React app

    With this pattern, requests are protected with:

    • sessions (and CSRF, but not tokens) between the front-end (React, Angular, Vue, etc.) and the gateway
    • tokens (not sessions nor CSRF) between the gateway and downstream REST APIs (which should be configured as stateless resource servers).

    If you inspect about any serious OAuth2 app with your browser debugging tools (Gmail, Facebook, LinkedIn, etc.), you'll find session cookies but no Authorization header with a Bearer token. You won't even find an OAuth2 token anywhere in your browser. The reason is that they apply this pattern...