Contents

Micronaut OIDC with Twitch webp image

In this post, I'd like to present how Micronaut integrates with OpenId Connect providers and how we can use Twitch as an authentication provider. You may find the configuration and classes mentioned below in a dedicated project on GitHub.

Brief intro

I'm not going to provide you with a full course into OAuth2 and OpenId Connect standards since you can find tons of blogs, lectures, and videos on these topics on the Internet. However, let's digest the two to have basic assumptions in mind when reading further.

What is OAuth2?

Shortly speaking, OAuth2 is a protocol describing (and unifying) a way of accessing secured resources on behalf of the owner of resources.

As a resource owner, I am giving access to my resources to a client. Resources sit on the resources server and require an access token to be available to a requestee.

A client - which could be an external application or service - has to be registered in the OAuth2 provider. It requests resources based on an access token. The client asks to authorize a specific scope - a set of granular permissions to resources. Permissions vary depending on the available resources: profile access, calendar view, followers lists, email box, etc. By providing consent, you are giving access to specified resources.

The client may get the access token using one of two flows:

  • implicit, where the provider returns the token directly from the authorization endpoint,
  • or authorization code, where a client first gets the code from the provider and then exchanges it for the token using a dedicated token endpoint.

What is OpenId?

On the other hand, OpenID Connect authenticates users in an application. Thus, you can sign up for it without the requirement of creating a separate user account there.

It is a layer provided on top of the OAuth2 and adds additional scope, openid, that the client can request in the first call.

The access token created with OAuth2 tells nothing about the resources' owner (at least not in a standardized way). It is a "gibberish" key that a client may use to access the selected resources.

On the other hand, the token created with OIDC provides permissions and information about the resource owner, i.e., the ID token. It has a specific format, JSON Web Token, and is decodable by the client to grab information embedded into the token. Additionally, OIDC specifies a dedicated endpoint for fetching more detailed information about your identity.

You might also be interested in:

CTA-micronaut-quarkus

Integration with Micronaut

Extensions

Micronaut provides a decent integration with OAuth2 and OIDC standards. It uses dedicated extensions, which are Micronaut Security OAuth2 and JWT. The latter is required to handle JWT since OpenId is based on it.

Implementations

The framework supports out-of-the-box integrations with OIDC providers like Auth0, Amazon Cognito, Okta, Google, and Keycloak.

Endpoints

Micronaut comes with a predefined flow and many predefined endpoints that ease setting up all the authorization stuff.

The process starts with the standard login endpoint provided by Micronaut (/oauth/login/{provider_name}). After receiving the login request, it redirects the flow to the authorization endpoint on the provider side.

Another important one is the default callback endpoint (i.e., /oauth/callback/{provider_name}). It handles a request from the provider after the authorization of a client. For example, it extracts the authorization code.

Next, the code is used to call the token endpoint on the provider side. If we use authorization code flow, the authorization code received by the callback endpoint is automatically exchanged for the token. Therefore, we don't need to configure it unless the provider has the endpoint exposed in a different way than the standard specifies.

Shortly about configuration settings

To set up OIDC, we need to register our application with the chosen provider to get the client's secret and id. Next, we need to put both values and the issuer URL in the configuration of the OAuth2 client.

What distinguishes OAuth2 from the OIDC configuration is providing openid in the scopes parameter. Thanks to this, the provider knows that it should use OIDC to authorize a user and return the access token and the OpenId token in response to the authorization request.

Next, in the openid section of the client configuration, we provide various endpoints. These are:

  • issuer (the unique URI of the party issuing tokens; it's used to match the iss claim from the received id token and to get metadata of the provider),
  • jwks-uri (URI to a set of public keys used to verify JWT returned by the provider),
  • authorization (endpoint used to authenticate and authorize a user; here, we can provide other settings like type of authorization, display type, and so on),
  • user-info (endpoint for fetching additional data about a user).

The user info endpoint is not used during the OIDC flow. However, we may call for more detailed user info in a separate request (more details below, for the Twitch case).

For more configuration details, you can check the documentation of Micronaut Security and tutorials available (search for the security tag).

Twitch example

In one of the previous projects, I had to base the authentication mechanism on the OIDC provided by Twitch. After connecting and authenticating a user, we could use the account data (like the profile stats of a streamer) in the application. Of course, we worked with the Micronaut framework.

We used the authorization code flow in the case above, and I'd like to focus here on this approach.

Yet another constraint - we were using RBAC to authorize users to call REST endpoints in the application. Thus, we needed to somehow map the Twitch ID token into user roles since the token provides no clues on the roles in claims. Fortunately, Micronaut has a solution for this: the OpenIdAuthenticationMapper interface. We may overwrite it to complement user data by calling the user-info endpoint manually.

Configuration

So, how can we set up the configuration? The first step is registering the application in the developer console. We enter the redirection URL in the registration form, which is the callback endpoint for the OIDC provider in the Micronaut application. The registered app receives id and secret values that have to be placed in the Micronaut configuration.

The configuration file filled with the Twitch specific data looks like the following:

oauth2:
  clients:
    twitch:
      client-id: your-app-id-from-twitch
      client-secret: your-app-secret-from-twitch
      scopes:
        - openid
        - user:read:email
      openid:
        issuer: https://id.twitch.tv/oauth2
        jwks-uri: https://id.twitch.tv/oauth2/keys
        authorization:
          url: https://id.twitch.tv/oauth2/authorize
          response-type: code
          display: popup
          max-age: 3600
        user-info:
          url: https://id.twitch.tv/oauth2/userinfo
  openid:
    additional-claims:
      access-token: true
      refresh-token: true

Notice that I have set additional claims as well. This way, Twitch returns both access and refresh tokens as claims in the ID token. We may use them to call Twitch API, for example, the user information endpoint.

Also, notice the scopes I have set above. With this configuration, we inform Twitch that we'll be using the OIDC instead of OAuth2 and we'd like to read the user's email.

Unfortunately, providing the scope is not enough to get the email value from Twitch in the id token as a claim. Since the email is not in the standard set of claims, we would have to specify it when calling the authorization endpoint explicitly. And that's the thing that Micronaut does not support. Thus, we need to call the user-info endpoint manually to get the email.

More on the SoftwareMill Tech Blog:

CTA-how-to-communicate-microservices

Classes

Next, we had to provide customized versions of a few Micronaut classes.

The first one is the deserializer of the OpenId token (OpenIdTokenResponseDeserializer). The class is required to properly deserialize an array of scopes provided by the Twitch auth service.

The second customized class is the authentication mapper (OpenIdAuthenticationMapper). The task of the default implementation is to map the received OIDC authentication to a user entity. The customized version calls for user details to get a user email and set a proper access role.

Summing up

Thanks to the framework design, extending its capabilities in the OIDC area is pretty straightforward. As you've seen above, providing a couple of dedicated classes to add Twitch authorization is just a matter of implementing a couple of dedicated classes. After that, adding yet another one shouldn't be hard as well!

As I stated in the beginning - you may check a sample project on GitHub. There, you will see all the settings provided and the customized classes.

Blog Comments powered by Disqus.