A few months ago, I was implementing an OpenID Connect server to manage access for hundreds of our internal applications. We moved from our own solutions, which were convenient on a smaller scale, to a widely accepted standard. Access through a central service significantly simplifies monotonous operations, reduces the costs associated with authorization implementations, allows for the discovery of many ready-made solutions, and alleviates the headaches involved in developing new ones. In this article, I will discuss this transition and the challenges we encountered.

Once upon a time... How it all began
Several years ago, when the number of internal applications became too large for manual management, we wrote an application for access control within the company. It was a simple Rails application that connected to a database containing employee information, where access to various functionalities was configured. It was then that we established our first SSO, which was based on token verification between the client and the authorization server; the token was transmitted in an encrypted form with several parameters and validated on the authorization server. This was not the most convenient option, as a significant layer of logic had to be defined for each internal application, and the employee databases were not synchronized with the authorization server at all.
After some time, we decided to simplify the task of centralized authorization. We transferred the SSO to a load balancer. Using OpenResty with Lua, we added a template that checked tokens, identified which application was being requested, and could verify if access was granted. This approach greatly simplified the task of controlling access to internal applications—there was no longer a need to define additional logic in the code of each application. As a result, we shut down external traffic, and the application itself was unaware of the authorization process.
However, one problem remains unresolved. How do we handle applications that need employee information? We could write an API for the authorization service, but then we would have to add additional logic for each of those applications. Furthermore, we wanted to eliminate our dependency on a specific custom-built application that we plan to transition to OpenSource and our internal authorization server. We will discuss this another time. The solution to both problems turned out to be OAuth.
Common standards
OAuth is a clear, widely accepted authorization standard, but since its functionality alone is insufficient, we started considering OpenID Connect (OIDC) right away. OIDC is, in fact, the third implementation of an open authentication standard that has evolved into an extension of the OAuth 2.0 protocol (an open authorization protocol). This solution addresses the lack of data about the end user and also allows for changing the authorization provider.
However, we did not choose a specific provider and decided to integrate OIDC with our existing authorization server. The flexibility of OIDC regarding end-user authorization was a key factor in this decision. This way, we could implement OIDC support on our current authorization server.

Our path to implementing our own OIDC server
1) We formatted the data appropriately
For OIDC integration, it is necessary to format the current user data in a way that is understandable by the standard. In OIDC, this is called Claims. Claims are essentially the final fields in the user database (name, email, phone, etc.). There is a , and anything not on this list is considered custom. Therefore, the first aspect to consider when choosing an existing OIDC provider is the ease of customization for new claims.
Groups of claims are combined into the following subset – Scope. During authorization, the request is for access not to specific claims, but to scopes, even if some claims from the scope are not needed.
2) We implemented the necessary grants
The next part of OIDC integration involves selecting and implementing authorization types, known as grants. The chosen grant will dictate the subsequent interaction scenario between the selected application and the authorization server. An approximate scheme for selecting the appropriate grant is shown in the diagram below.

For our first application, we used the most common grant – Authorization Code. Its distinguishing feature from others is that it is a three-step process, meaning it undergoes an additional verification. First, the user makes a request for authorization consent, receives a token – Authorization Code, and then with this token, like a ticket for travel, requests an access token. The main interaction of this authorization scenario is based on redirects between the application and the authorization server. You can read more about this grant .
OAuth adheres to the concept that access tokens obtained after authorization should be temporary and ideally changed approximately every 10 minutes. The Authorization Code grant is a three-step verification through redirects, and performing such steps every 10 minutes, to be honest, isn’t the most visually pleasing task. To address this issue, there is another grant – Refresh Token, which we also implemented. This one is simpler. During verification with another grant, in addition to the main access token, another one – Refresh Token, is issued, which can only be used once and its lifespan is typically significantly longer. With this Refresh Token, when the TTL (Time to Live) of the main access token expires, a request for a new access token will go to the endpoint of another grant. The used Refresh Token is immediately invalidated. This verification is two-step and can be executed in the background, unnoticed by the user.
3) Configured the output formats for user data
After the selected grants are implemented and authorization is functioning, it's important to mention obtaining end-user data. There is a separate endpoint in OIDC for this, where, with your current access token and its validity, you can request user information. If user data does not change frequently and if you need to access it multiple times, a solution like JWT tokens can be adopted. These tokens are also supported by the standard. A JWT token consists of three parts: header (information about the token), payload (any necessary data), and signature (the token is signed by the server, and the source of its signature can be verified later).
In the OIDC implementation, a JWT token is called an id_token. It can be requested along with the regular access token, and all that remains is to verify the signature. For this, the authorization server has a separate endpoint with a set of public keys in the format . Furthermore, it should be noted that there is another endpoint that reflects the current configuration of the OIDC server based on the standard , which contains all the addresses of the endpoints (including the address for the set of public keys used for signing), supported claims and scopes, used encryption algorithms, supported grants, etc.
For example, in Google:
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"locale",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
]
}Thus, using the id_token, it is possible to pass all the necessary claims in the token's payload without needing to contact the authorization server each time for user data. The downside of this approach is that changes in user data from the server do not come immediately, but rather with the new access token.
Implementation Results
After implementing our own OIDC server and configuring connections to it on the application side, we resolved the issue of user information transmission.
Since OIDC is an open standard, we have the option to choose an existing provider or implement our own server. We tried Keycloak, which turned out to be very convenient to configure; after setup and changing the connection configurations on the application side, it is ready for use. On the application side, only the connection configurations need to be changed.
Regarding existing solutions
Within our organization, we built our own implementation as the first OIDC server, which was supplemented as needed. After a detailed review of other ready-made solutions, it can be said that this is a contentious issue. The decision to implement our own server was influenced by concerns from providers regarding the lack of necessary functionality, as well as the existence of an old system that had various custom authorizations for certain services and already stored a significant amount of employee data. However, the existing implementations offer conveniences for integration. For example, Keycloak has its own user management system, and data is stored directly within it, so migrating our users there is not a major task. Keycloak provides an API that allows for all necessary migration actions.
Another example of a certified and, in my opinion, interesting implementation is Ory Hydra. It is interesting because it consists of different components. For integration, you will need to link your user management service with their authorization service and expand as necessary.
Keycloak and Ory Hydra are not the only ready-made solutions. It's best to choose a certified OpenID Foundation implementation. Usually, such solutions feature the OpenID Certification badge.

Also, don't forget about existing paid providers if you don't want to host your own OIDC server. There are many good options available today.
What's Next
In the near future, we plan to close off traffic to internal services in another way. We intend to migrate our current SSO to a load balancer using OpenResty via a proxy based on OAuth. There are already many ready-made solutions available for this, such as:
Additional materials
– a good service for verifying JWT tokens
— a list of certified OIDC implementations
Source: habr.com
