Search code examples
oauth-2.0microservices

What is a good microservice-friendly technique for refresh token security?


I've got an OAuth2-driven authorization system for the applications in my ecosystem using the Auth Code flow. At the moment, it is working extremely well following what I feel are best practices. My current flow can be summed up as this:

  1. User clicks a Login button in application frontend.
  2. Backend for application redirects user to Auth Server login page with redirect URI, client ID, etc, in params.
  3. User logs in, Auth Server redirects to Backend /authcode endpoint with Authorization Code.
  4. Backend authenticates with Authorization Code, receives Access (JWT) & Refresh tokens. It stores Refresh token in its own database, and returns Access token as an HTTP-Only cookie.
  5. When Access token expires, Backend sends Refresh token to Auth Server to refresh.

The problem with this approach is it is heavily dependent on a single-backend/monolith/etc architecture. When the access token expires, the backend app in question needs to be able to get the refresh token and then do the refresh against the Auth Server.

My goals for the future state of this architecture are driven by two main concerns:

  1. Allowing backends to be split into multiple micro-services that are all capable of authenticating the same JWT.
  2. Supporting SSO. Currently, even though all users are managed through the same Auth Server and use the same credentials, they need to enter those credentials separately for each app they log into.

My initial thought was to move the refresh handling to the client-side part of the architecture. Since all the apps are owned by me, I could setup a new flow where a user would login directly with the Auth Server, and then the Access (JWT) and Refresh tokens are set as HTTP Only cookies. This is further supported by the fact that all of my apps are hosted via a single domain name (with different root URI paths for different apps). The new token structure would determine which apps a given user has access to.

My concern there is how to secure the Refresh Token client-side. Most likely I would need to use the Path attribute and depend on the fact that all my apps are on the same hostname, which as I said they are and that will not be changing.

I guess the point of this post is I'm looking for guidance on best practices for handling this kind of scenario.


Solution

  • A few thoughts based on design patterns we recommend at Curity:

    BACKENDS AND JWTs

    Related APIs should be able to forward JWT access tokens to each other, as detailed in the scopes article. Validating the JWT in each API results in a recommended zero trust setup.

    BACK END FOR FRONT END

    We recommend a particular way of doing this, focused on separation of web and API concerns - see this code example on the token handler pattern.

    Token refresh is handled via HTTP Only cookies and client side retries. This provides a stateless solution that is easy to manage, and where the web back end is static content only, eg a content delivery network. It requires a couple utility API components to be deployed though.

    An alternative option, as you say, is to write code within your back end to store tokens, so that cookies only contain a Session ID.

    COOKIES AND MULTIPLE WEB APPS

    In our resources we use reverse proxies / API gateways as the entry point to APIs, rather than a web back end. Common plumbing such as translating cookies to tokens can then be managed via gateway plugins - as covered in this tutorial.

    However, when you have multiple web apps you need to keep cookies isolated during API requests. Each app therefore needs its own API routes. This is awkward but is a consequence of needing to use secure cookies for best browser security. It is best managed in the gateway, and separate domains or subdomains is usually cleanest:

    Each web team is then responsible for their own API routes and the cookie / CORS / CSRF stuff, rather than the API developers.

    SSO

    This should be determined solely by the Identity Provider session cookie, which will remain valid as you navigate across apps. If you are presenting a login button when the app does not have a secure cookie yet, then it will not feel like SSO though. One technique for making SSO look seamless is for each app to automatically redirect when it does not have application cookies yet, rather than presenting a login button.

    SUMMARY

    A long answer, but the best solutions require separation of concerns that is not always obvious. Done well, the end result should be simple code in apps, correct security and an architecture that can be scaled.