The complete guide to integrating an OIDC server into your project
Learn the best practices of integrating an OIDC (OpenID Connect) server into your project and understand how components interact with each other on the stage.
Introduction
You may encounter a situation where you need a centralized authentication and authorization system, a.k.a. identity access management (IAM) or identity provider (IdP). Sometimes people will append a word to designate the business, such as Customer IAM and Workforce IAM.
Let's set aside these fancy names for a moment. The need for IAM could arise because your application is growing, or you plan to delegate the hard work to a vendor from the start. Nevertheless, you are reaching a point where an identity system will need to be introduced to your project.
Considering the popularity of OAuth 2.0, OpenID Connect (OIDC) is a natural choice for many developers. Since OIDC is an authentication layer built on top of OAuth 2.0, you may feel familiar when you start to work with OIDC. Let's get started!
But first, what and why?
Feel free to skip this section if you are already familiar with the concept of identity provider and the importance of it.
An OIDC server, or identity provider, is a centralized system that manages user authentication and authorization. As we discussed in Why you need a centralized identity system for a multi-app business, a centralized identity system has many benefits.
Let's say your project starts with a simple web application, and it has authentication built-in:
As your project grows, you need to introduce a mobile version:
It'll be bad experience for users if they need to create an account for each application. Since you started with a web application, you let the mobile application communicate with the web application for authentication:
Now, a new API service is being introduced. Since it's a service for paid users, you need to make sure the user is authenticated and authorized to access the service. To achieve this, you can proxy the service through the web application:
Or, use some token technique to authenticate the user, and validate the token by talking to the web application in the service. Therefore the mobile application can use the service directly:
Things are getting messy. So you decide to split the authentication and authorization logic into a separate service:
The refactoring process can be painful. You may notice that the complexity of it will increase exponentially as you add more applications and services to the project. Even worse, you may need to maintain multiple authentication methods such as passwordless sign-in, social login, SAML, etc.
This is why we'd better introduce an identity provider at the beginning when you have a plan to scale your project.
Integrate an OIDC server
Find an OIDC provider
There are many OIDC providers available in the market. You can choose one based on your requirements and preferences. As long as the provider is OIDC compliant, it will play the same role in your project.
Subject, client, and audience
To simplify the concept, we can think of the subject is the entity that is requesting access to an audience via a client.
Let's see some typical scenarios:
1. A user clicks the sign-in button on a web application
In a traditional and server-side rendering web application, the frontend and backend are coupled. Let's say the web application serves both frontend and backend:
- Subject: The user
- Audience: The OIDC server
- Client: The web application
It may looks counter-intuitive that the audience is the OIDC server. In fact, it is the key to realize the SSO (Single Sign-On) experience for end-users. Let's see a simplified sequence diagram for authorization code flow:
code
is a one-time code that can be exchanged for various tokens, such as access token, ID token, and refresh token. It's OK if you don't understand all these tokens at this moment. As we move forward, you'll get a better understanding of them.
In the above case, the user doesn't need to sign in again when they switch to another application because the user (subject) has already authenticated with the OIDC server (audience).
2. A user uses a single-page application
In a single-page application (or a mobile application), the frontend and backend are separated. Let's say the backend is an API service:
- Subject: The user
- Audience: The API service
- Client: The single-page application (SPA)
A simplified sequence diagram with authorization code flow:
Since the API service is non-interactive, the SPA needs to use the access token with the API service as the audience (the aud
in the token).
Why the OIDC server is still an audience?
Technically, you can remove the OIDC server from the audience list. Since in most cases, you'll need the user information from the OIDC server (which requires the OIDC server to be the audience), it's better to always include the OIDC server in the audience list when it involves user interaction.
Wait, you are saying that we can have multiple audiences in an authorization request?
Exactly! Remember that OIDC is built on top of OAuth 2.0, it is possible to leverage RFC 8707: Resource Indicators for OAuth 2.0 in the authorization request to specify multiple audiences. It requires the support from both the grant and the OIDC server. Logto supports this feature natively.
3. A machine-to-machine communication
Let's say you have a service A that needs to call service B:
- Subject: Service A
- Audience: Service B
- Client: Service A
A simplified sequence diagram with client credentials grant:
When service B needs to call service A, the roles are simply swapped.
Recap
- Subject: It can be a user, a service, or any entity that needs to access the audience.
- Client: It can be a web application, a mobile application, or any entity that initiates the request or acts on behalf of the subject.
- Audience: It can be a service, an API, or any entity that provides access to the subject.
Tokens
There are three types of tokens you may encounter when working with OIDC:
- Access token: It is used to access the audience. It can be a JWT (JSON Web Token) or an opaque token (usually a random string).
- ID token: An OIDC-specific token that contains user information. It is always a JWT. The client can decode the token to get user information.
- Refresh token: It is used to get a new set of tokens when the access token or ID token expires.
For a detailed explanation of these tokens, you can refer to Understanding refresh tokens, access tokens, and ID tokens in OIDC protocol.
In above scenario 1 and 2, the term authorization request refers to a request to get a set of tokens via a specific grant.
When everything goes well, a set of tokens will be returned in the "Exchange tokens using code
" step. The available tokens in the set depend on multiple factors, especially the scope
parameter in the authorization request. For the sake of simplicity, we'll assume all tokens are returned in the set. Once the access token expires, the client can use the refresh token to get a new set of tokens without user interaction.
For scenario 3, it is simpler because the client credentials grant only returns an access token.
Multiple audiences
You may notice that only one access token is returned at a time. How could we handle the case where the client needs to access multiple audiences?
There are two common solutions:
Specify resource
in the code exchange request
When the client exchanges the code for tokens, it can specify a resource
parameter in the request. The OIDC server will return an access token for the specified audience if applicable.
Here's a non-normative example:
Then the OIDC server will return an access token for the API_SERVICE
audience, if applicable.
Use a refresh token to get a new access token
With RFC 8707, the client can even specify multiple audiences by using the resource
parameter multiple times. Now, if refresh tokens is available in the client, the client may specify the audience in the resource
parameter when refreshing the token.
Here's a non-normative example:
It has the same effect as the previous solution. Meanwhile, other granted audiences are still available in future token requests.
Client credentials grant
You can also use the resource
parameter in the client credentials grant to specify the audience. There's no issue with multiple audiences in this grant because you can always request a new access token for a different audience by simply sending another token request.
Protect your API service
The "API service" in scenario 2 and the "Service B" in scenario 3 have one thing in common: they need to validate the access token to determine if the request is authorized. Depending on the format of the access token, the validation process may vary.
- Opaque token: The API service needs to call the OIDC server to validate the token. An introspection endpoint is usually provided by the OIDC server for this purpose.
- JWT: The API service can validate the token locally by checking the signature and the claims in the token. The OIDC server usually provides a JSON Web Key Set (JWKS) endpoint (
jwks_uri
) for the API service to get the public key to verify the signature.
If you are new to JWT, you can refer to What is JSON Web Token (JWT)?. In fact, usually there's no need to validate the signature and assert the claims manually because there are many libraries that can do it for you, such as jose for Node.js and web browsers.
Assert claims
In addition to validating the JWT signature, the API service should always check the claims in the token:
iss
: The issuer of the token. It should match the OIDC server's issuer URL.aud
: The audience of the token. It should match the API service's audience value (usually a valid URI).exp
: The expiration time of the token. The API service should reject the token if it's expired.scope
: The scopes (permissions) of the token. The API service should check if the required scope is present in the token.
Other claims, such as sub
(subject) and iat
(issued at), are also important in some cases. If you have additional security measures, check the claims accordingly.
Authorization
An unanswered question remains out there: How do we determine if a scope (i.e. permission) can be granted to a subject?
The single question leads to a whole new world of authorization, which is out of the scope of this article. In short, there are some common approaches like RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control). Here are some resources to get you started:
- RBAC and ABAC: The access control models you should know
- Mastering RBAC in Logto: A Comprehensive Real-World Example
Closing notes
Introducing an OIDC server into your project is a big step. It can significantly improve the security and scalability of your project. Meanwhile, it may take some time to understand the concepts and the interactions between components.
Choosing a good OIDC provider that fits your requirements and preferences can notably reduce the complexity of the integration process, as the provider usually offers the whole package, including the OIDC server, authorization mechanisms, SDKs, and the enterprise features you may need in the future.
I hope this guide helps you understand the basics of integrating an OIDC server. If you are looking for one to start with, I would selfishly recommend Logto, our identity infrastructure for developers.