Every so often, on Sundays (obviously), Commit engineering partners share an open source software tool they use to make their work and life easier. This week’s feature, by David Cheung, a Commit software engineer, is on Oathkeeper.
What are we doing?
I’m part of a team building a platform that requires login and user management functions.
We’re using a combination of Auth0, widely used for user management, and ORY Oathkeeper, an open-source solution.
The combination of Oathkeeper and Auth0 is not a common use case, but it’s an elegant configuration-driven setup. It enables us to decouple our business logic from our user sign-in logic. That’s beneficial for microservice architecture scenarios — as our application scales, we can reuse our user authentication logic across all our services.
Moving away from monolith login features
We started out with a single service that handles the register, login, authentication, authorization, routing, and business logic. As the application and needs grow, we want to add more features and move our services to a more micro-service friendly setup. We also want to decouple the business logic from the user authentication.
From the application developer point of view, it’s also a clean and easy-to-use solution, as it puts all the auth responsibility on Oathkeeper. The application doesn’t need to verify any credentials, it just receives a header added by the proxy. If it exists, the user is logged in. If it doesn’t, they are not.
Using existing solutions
Using existing tools can give us feature-rich solutions that have been vetted in production and refined over time. Often we don’t realize how many components are in a seemingly simple feature such as login and and logout until we start scoping out requirements or even building it. For example, for user-management and surrounding features you need CSRF protection, password hashing, email verification, securely storing user credentials, social sign-in, token signing, security compliance, and the list goes on.
Requirements for auth solution
Stateless API and session store
A solution to keep our backend services all stateless, and keep the user session state outside of the application logic.
Users logging into the application would get a signed JWT token and return to the frontend, saved in a cookie (or localStorage) and passed upstream.
Once a user is logged in, the frontend application passes down the token via header/cookie on every subsequent request, then upstream services would verify and authenticate the request using the JWT token passed along with the requests.
Our tool of choice was Auth0. It’s not open-source software but there are a plethora of features and great tooling for it. Auth0 is “user-management as a service” software. It has wild adoption and great community support and resources, providing us with many features, such as:
- Login and sign-up interface and functionality
- Login via OIDC-compatible social login via OAuth2
- Login via username and password
- Email verification and password resets
- OpenID compliant support with token signing
- Upon sign in we get a JWT token from Auth0 with customizable expiry and metadata
- Built in security compliance link
- And much more (web GUI to handle users, webhooks, analytics, user roles, multi-factor authentication, etc.)
Oathkeeper is a proxy that supports many authentication and authorization features. It has many features to support sessions and allow us to authenticate, mutate, and route requests before reaching the business logic:
- Configuration driven proxy
- Authenticators (JWT support, fetching cookies from another service, oauth2 introspection)
- Authorizers (route-based, or remote service-based access-control rules)
- Mutators (setting headers or cookies based on session info, or updating the session with a remote service)
- Error handling
- Routing (proxying requests to microservices)
- Native Kubernetes support through their controller and custom resources
High-level setup overview
A brief explanation of the setup from the ground up:
- Cloud Infrastructure on Kubernetes
- Routing proxy (Oathkeeper allows requests to be routed to the authentication endpoints while safeguarding the rest of the endpoints, keeping the request on the same domain to make cookie management and CORS easier)
- Auth0 (user-identity service and storage)
- Session handling(Oathkeeper uses JWT authenticator to manage and verify the sessions)
- Auth endpoints to handle token exchange
- Backend application
- Frontend application
Topology of the setup is as follows:
Login Flow and Request flow
Setting up Auth0
In our case we have configured Auth0 following the regular web app flow. While submitting the authorization request we must include the scope
openId. Then Auth0 will return an ID token when obtaining userInfo, which in this example is the user’s token. This way we don’t need to maintain, rotate or deploy a set of private or public keys, implement code to sign or verify JWT tokens, or figure out how to secure user credentials or sessions in your database.
Proxy (Oathkeeper) setup
In the proxy we need to set up three parts:
Here we want to split up the authenticated routes, and routes to Login via `Rules` in Oathkeeper. The Oathkeeper controller (oathkeeper-maester) will pick up `Rules` applied to kubernetes then apply it to Oathkeeper’s config automatically.
Session creation (JWT authenticator)
Conveniently, Oathkeeper has a suite of plugins to handle common authentication methods. We’re using the id_token from Auth0 as the token and verifying it using the JWT authenticator, configuring the JWT plugin as follows:
Oathkeeper also provides other middleware such as mutation plugins, which can access the session data then inject metadata into the request downstream. In this case we want to remove authentication from the application logic while keeping the authorization and user lookup in the application, so we can pass the user-id and email to the application. This means that by the time the request gets to the application, it can check for the existing of the X-User-Id header. If it exists, the specified user is logged in. If a user isn’t logged in, the request won’t even reach the application.
You will need to implement a few endpoints to handle the interactions with Auth0. Note that you can also implement these in your backend application and route them to the same place, but for clarity the logic is represented as its own service:
Upon success, all three of these methods will redirect the user back to your frontend application.
To let the frontend determine whether a user is logged in or not, we can implement an endpoint like /whoami that checks the headers injected by Oathkeeper and returns the user info. Then you can create stateless API calls that check the user’s logged-in status on the frontend and leverage Auth0 as your logged-in state controller.
Here’s an example in node.js. You could have a middleware to look up the user, such as:
Login: To log in, you can simply redirect the user to the `<ingress>/auth0/login` and it will redirect you to the Auth0 login page, then bring you back to your application with the cookie set.
Check user status: With the endpoint in the backend implemented, in the frontend you can easily have a function as follows to determine if a user is signed in.
Oathkeeper is a useful open source tool that can be integrated with Auth0 to create an elegant solution for decoupling user authentication and application logic. This approach allows you to quickly build additional services sharing the user authentication setup. Consider this method when building your next application!
David Cheung is an Engineering Partner at Commit. He has been in software development for over a decade honing his expertise in full-stack development, and is passionate about open source projects.
Interested in becoming an Engineering Partner at Commit? We offer professional development, peer-to-peer support and access to exciting startups. Apply now.