Integration with External IdPs

Preface

This document is a tutorial to describe how to integrate an Authlete backed authorization server (AS) with external identity providers (IdPs) so that you can build API authorization infrastructure with highly secure industry standards like Financial-grade API (FAPI) while offloading user authentication and management functions to such IdPs.

This integration would be beneficial for service providers that have already built a repository of user credentials and information using IDaaS (Identity as a Service), and been required to implement FAPI-based security provisions defined by ecosystems such as UK Open Banking, Australian Consumer Data Right and Open Banking Brasil.

In this tutorial, you can see an integration example using Okta as an OpenID Connect (OIDC) IdP and java-oauth-server as an AS backed with Authlete.

Prerequisites

These accounts would be needed before you begin this tutorial:

You also need to have an environment where you can install and run java-oauth-server.

Identity federation between AS and IdP

How an AS authenticates end-users is intentionally not defined in the specifications of OAuth 2.0 (RFC 6749) and OIDC (OpenID Connect Core 1.0). Therefore, AS implementations may choose to delegate end-user authentication to an external OIDC IdP, and obtain information about the user from the IdP. The diagram below illustrates the flow of identity federation between an AS and an external OIDC IdP.

The following is explanation about the flow in the diagram in words.

  1. A client application sends an authorization request to the authorization endpoint of the authorization server (AS) via a web browser.
  2. The authorization endpoint of the AS generates an authorization page and returns it to the web browser. If the AS supports identity federation with external OpenID Providers, the authorization page includes links for identity federation.
  3. The end-user clicks a link for identity federation in the displayed authorization page. The click makes the web browser access a Web API ("(federation/initiation)" in the diagram) which initiates identity federation with the chosen OpenID Provider.
  4. The Web API builds an authentication request that conforms to OpenID Connect. The authentication request is a URL that points to the authorization endpoint of the OpenID Provider (OP) and includes request parameters in the query part.
  5. The Web API returns 302 Found (or whatever triggers redirection) with the Location header to the web browser in order to make the web browser send the authentication request to the authorization endpoint of the OP.
  6. The web browser sends the authentication request to the authorization endpoint of the OP.
  7. The authorization endpoint of the OP generates an authorization page and returns it to the web browser. The authorization page tells that the AS (not the Client) is requesting permissions.
  8. The end-user authenticates herself at the OP and authorizes the authorization request from the AS.
  9. The OP generates an authorization code and returns 302 Found (or whatever triggers redirection) with the Location header to the web browser in order to make the web browser access the redirection URI of the AS (not of the Client) with the authorization code. From a viewpoint of the OP, the AS is a client application and the redirection URI is one among those the AS has registered to the OP in advance.
  10. The redirection endpoint of the AS ("(federation/callback)" in the diagram) receives the authorization code.
  11. The redirection endpoint sends a token request to the token endpoint of the OP with the issued authorization code.
  12. The token endpoint of the OP generates an access token and an ID token and returns them.
  13. The redirection endpoint accesses the userinfo endpoint of the OP with the issued access token.
  14. The userinfo endpoint of the OP returns information about the end-user.
  15. The redirection endpoint regards that the end-user has logged in the AS, re-generates an authorization page and returns it to the web browser.
  16. The end-user authorizes the original authorization request from the Client.
  17. The AS generates an authorization code and returns 302 Found (or whatever triggers redirection) with the Location header to the web browser in order to make the web browser access the redirection URI of the Client (not of the AS) with the authorization code. This assumes that the Client has registered at least one redirection URI to the AS in advance.
  18. The redirection endpoint of the Client (“Redirection Endpoint” in the diagram) receives the authorization code.
  19. The Client sends a token request to the token endpoint of the AS with the authorization code which it received via the redirection endpoint.
  20. The token endpoint of the AS generates an access token and returns it to the Client.

The next sections will introduce how to configure Okta as an IdP and integrate it with an Authlete-backed AS (java-oauth-server) to enable the flow shown above.

Configuring IdP (Okta)

You need to create a new “App Integration” in the server which is hosted on Okta so that an AS can act as a client application of the OIDC IdP.

The table below shows points for App Integration settings.

Item Value
Sign-in method OIDC - OpenID Connect
Application Type Web Application
Sign-in redirect URIs http://localhost:8080/api/federation/callback/okta
Controlled access Allow everyone in your organization to access

You may have noticed that the value of “Sign-in redirect URIs” implies that this tutorial will later instruct you to run an AS on your local machine at the port number 8080 and that the AS provides /api/federation/callback/okta API.

Images below are screenshots of App Integration settings.

After you create the App Integration, Okta issues a pair of Client ID and Client secret. The pair will be used in the next section.

Configuring AS (java-oauth-server)

In this tutorial, we use java-oauth-server as an AS. It is an open-source sample implementation of AS that uses Authlete as backend.

First, download java-oauth-server and edit authlete.properties. You need to change the values of service.api_key and service.api_secret in the file at least.

$ git clone https://github.com/authlete/java-oauth-server
$ cd java-oauth-server
$ vi authlete.properties

Second, open federations.json with your text editor. This file describes configurations of identity federations. The initial content looks like below. Replace YOUR_COMPANY, YOUR_CLIENT_ID and YOUR_CLIENT_SECRET with actual values which have been assigned to you from Okta. See Appendix/federations.json for details about federations.json.

{
  "federations": [
    {
      "id": "okta",
      "server": {
        "name": "Okta-hosted IdP",
        "issuer": "https://YOUR_COMPANY.okta.com"
      },
      "client": {
        "clientId": "YOUR_CLIENT_ID",
        "clientSecret": "YOUR_CLIENT_SECRET",
        "redirectUri": "http://localhost:8080/api/federation/callback/okta",
        "idTokenSignedResponseAlg": "RS256"
      }
    }
  ]
}

The above are all the necessary configuration. Start java-oauth-server on your local machine.

$ docker-compose up
    # or mvn jetty:run

If you confirm logs like below, java-oauth-server is ready to work.

app_1  | [INFO] Started ServerConnector@7c5ae5c3{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
app_1  | [INFO] Started @41702ms
app_1  | [INFO] Started Jetty Server

Walk-through

In the diagram shown in the “Identity federation between AS and IdP” section, the client uses the OIDC authorization code flow (response_type=code). But here, to quickly confirm that identity federation with Okta works, we use the OIDC implicit flow which issues an ID token only (response_type=id_token).

The URL below is an authorization request. Replace CLIENT_ID with a client ID issued from Authlete (not from Okta) and SERVICE_API_KEY with a service API key issued from Authlete, and then copy and paste the URL to the address bar of your web browser.

http://localhost:8080/api/authorization?client_id=CLIENT_ID&response_type=id_token&scope=address+email+openid+phone+profile&redirect_uri=https://api.authlete.com/api/mock/redirection/SERVICE_API_KEY&nonce=mynonce&state=mystate&prompt=login

You’ll see an authorization page like below. Click the link “Okta-hosted IdP” which is located over the “Authorize” button.

The web browser will display the authorization page like below which was generated by Okta. Input your login ID and password which you use to login the server hosted on Okta.

The web browser will re-display the original authorization page generated by java-oauth-server. This time, java-oauth-server regards that you have logged in. Press the “Authorize” button.

java-oauth-server will issue an ID token and redirect the web browser to the redirection endpoint. The mock implementation of the redirection endpoint hosted on an Authlete server (/api/mock/redirection/SERVICE_API_KEY) displays parameters it receives.

When a request includes an unencrypted ID token, the redirection endpoint decodes the payload part of the ID token and shows it like below.

The table below indicates data sources of claims in the issued ID token. All the user attributes originate from Okta.

Claim Data Source
email Okta
email_verified Okta
family_name Okta
given_name Okta
locale Okta
name Okta
phone_number_verified Okta
preferred_username Okta
updated_at Okta
zoneinfo Okta
iss Authlete / Service configuration, issuer
sub java-oauth-server / subject parameter passed to Authlete’s /api/auth/authorization/issue API
aud Authlete / Client configuration, clientId (unmodifiable)
exp Authlete / expiration date of the ID token
iat Authlete / issue date of the ID token
auth_time java-oauth-server / authTime parameter passed to Authlete’s /api/auth/authorization/issue API
nonce Client / the value of the nonce request parameter
s_hash Authlete / computed based on the state request parameter

You may wonder where the value of the sub claim ("00ucabhbjLVphPrXF696@okta" in the example) originates from. It is just a result of concatenating the value of the sub claim returned from Okta with the string "@okta". The concatenation is conducted in createUserEntity() method in FederationEndpoint.java. There is no big meaning there and it is up to you how to determine the value of the sub claim.

Conclusion

In this tutorial, we were able to confirm an integration pattern between an Authlete backed authorization server and an external identity provider, and a real walk-through using java-oauth-server and Okta.

Appendix

Implmenentation Details

You can learn implementation-level details about identity federation by looking into Federation.java and FederationEndpoint.java of java-oauth-server.

After receiving user information from the userinfo endpoint of an OIDC IdP, the current implementation of java-oauth-server registers a user record into its on-memory user database. However, commercial implementations may choose other approaches.

For example, an implementation may avoid copying user data from an OIDC IdP into its database and instead just remember the access token issued from the IdP so that the implementation can retrieve up-to-date user information from the userinfo endpoint of the IdP at any time it wants to.

To implement such behavior, the AS may prepare a database to remember access tokens issued from IdPs. Another possible approach is to use Authlete’s Extra Properties feature with which an AS can associate arbitrary key-value pairs with an access token and make Authlete remember IdP-issued access tokens. (cf. “How to add extra properties to an access token”)

federations.json

federations.json is a configuration file that describes configurations of identity federations. java-oauth-server loads the file at the first time it receives an authorization request. Identity federations listed in the file will appear in the authorization page of java-oauth-server.

The default location of the file is "federations.json". The environment variable FEDERATIONS_FILE and the system property federations.file can be used to specify a different location of the file.

The content of federations.json must be a JSON object which has "federations" as a top-level property like below.

{
  "federations": [
    "(configurations of identity federations)"
  ]
}

Each entry in the "federations" array represents a configuration of identity federation and must be a JSON object which has "id", "server" and "client" as top-level properties.

{
  "federations": [
    {
      "id": "(unique identifier among the configurations)",
      "server": {
        "(server configuration)"
      },
      "client": {
        "(client configuration)"
      }
    }
  ]
}

The value of "id" is used as federationId in the following API paths of java-oauth-server.

  • /api/federation/initiation/federationId
  • /api/federation/callback/federationId

"server" describes the configuration of the target OpenID Provider. Its value is a JSON object which has properties shown in the table below.

Property Description
name The display name of the OpenID Provider. This is used in the authorization page.
issuer The issuer identifier of the OpenID Provider. The value must match the value of "issuer" in the discovery document of the OpenID Provider. The OpenID Provider must expose its discovery document at {issuer}/.well-known/openid-configuration so that identity federation of java-oauth-server can work.

"client" describes the configuration of the Relying Party, which is always java-oauth-server. Note that java-oauth-server is a client application from a viewpoint of external OpenID Providers. The value of "client" is a JSON object which has properties shown in the table below.

Property Description
clientId Client ID issued by the OpenID Provider.
clientSecret Client secret issued by the OpenID Provider. If this property is set, token requests sent to the token endpoint of the OpenID Provider will include an Authorization header for client authentication. This behavior assumes that the token endpoint supports client_secret_basic as a method of client authentication.
redirectUri A redirect URI that you have registered into the OpenID Provider.
idTokenSignedResponseAlg The algorithm the OpenID Provider uses when it signs ID tokens. If this property is omitted, "RS256" is used as the default value.