ForgeRock AM OIDC / OAuth 2 sample flows

Rory Braybrook
The new control plane
8 min readJun 15, 2020

This is a summary of the flows and examples using curl.

Deciding Which Flow to Use Depending on the Relying Party

A “Relying Party” is a web application running on a server. For example, a .war application.

For OpenId Connect:

Authorization Code

The OpenID Connect provider uses the user-agent, for example, the end user’s browser, to transport a code that is later exchanged for an ID token (and/or an access token).

For security purposes, you should use the Authorization Code grant with PKCE when possible.

The relying party is a native application or a single-page application (SPA). For example, a desktop, a mobile application, or a JavaScript application.

Authorization Code with PKCE

Since the relying party does not communicate securely with the OpenID Connect provider, the code may be intercepted by malicious users. The implementation of the Proof Key for Code Exchange (PKCE) standard mitigates against those attacks.

The relying party knows the user’s identifier, and wishes to gain consent for an operation from the user by means of a separate authentication device.

Backchannel Request Grant

The relying party does not require that the user interacts directly with it; instead it can initiate a backchannel request to the user’s authentication device, such as a mobile phone with an authenticator app installed, to authenticate the user and consent to the operation.

For example, a smart speaker wants to authenticate and gain consent from its registered user after receiving a voice request to transfer money to a third-party.

The relying party is an SPA. For example, a JavaScript application.

Implicit

The OpenID Connect provider uses the user-agent, for example, the end user’s browser, to transport an ID token (and maybe an access token) to the relying party. Therefore, the tokens might be exposed to the end user and other applications.

For security purposes, you should use the Authorization Code grant with PKCE when possible.

The relying party is an application that can use the ID token immediately, and then request an access token and/or a refresh token.

Hybrid

AM uses the user-agent, for example, the end user’s browser, to transport any combination of ID token, access token, and authorization code to the relying party.

The relying party uses the ID token immediately. Later on, it can use either the access token to request a refresh token, or the authorization code to request an access token.

For security purposes, you should implement the PKCE specification when using the Hybrid flow when possible.

OAuth2 flows

AM supports the following OAuth2 flows:

  • Authorisation Grant
  • Implicit Grant
  • Resource Owner Password Credentials Grant
  • Client Credentials Grant
  • Device Flow (now called Device Authorization Grant)

Device Flow is for “headless” devices that have Internet access. Essentially you use e.g. a mobile phone browser for the UI and the keyboard input. So e.g. you push a button on the device and it displays a URL and a code. You go to the URL on your phone, authenticate, enter the code and then the device lets you in.

It also implements:

  • Remote Consent Service
  • JWT Bearer Profile
  • SAML 2.0 Bearer Assertion Profile

The Remote Consent service allows the consent-gathering part of an OAuth 2.0 flow to be handed off to a separate service.

The JWT Bearer profile allows you to use a JWT for authentication instead of e.g. using the resource owner password flow.

The SAML profile allows you to exchange a SAML XML token for a JWT that you can then pass to a REST API.

AM supports the following OpenID Connect grant types and standards:

Grant Types

  • Authorization Code
  • Authorization Code with PKCE
  • Back Channel Request
  • Implicit
  • Hybrid
  • Hybrid with PKCE

Before all the grant types will work, you need to enable them:

Note that the authentication method is “client_secret_basic”.

Before you start, set up a Bash session:

Using Git Bash, start an interactive bash shell in the am container by running the following command:

winpty docker exec -it am //bin/bash

Authorization Code Grant

(Note: This flow is considered insecure. Best practice is to use Authorization Code Grant with PKCE).

Here we authenticate to the “authorize” endpoint and get back a “code”.

We then send the code to an “access_token“ endpoint and get back an access token and an ID token.

We get the id token because we specify “scope=openid“ in the request.

If we specify “offline_access”, we get back a refresh token as well.

In a private browser session, run:

http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/authorize?client_id=YourApp&redirect_uri=https://oidcdebugger.com/debug&scope=openid profile&response_type=code&response_mode=form_post

(Note: You can do this without a browser by referring to “To Obtain an Authorization Code Without Using a Browser in the Authorization Code Grant Flow“ in the documentation).

Note the use of a test tool — oidcdebugger.com.

This brings up:

Authenticate with your credentials.

Then:

“Allow”

Back in the Bash session:

$ AUTH_CODE=code as above

Then:

$ curl -k -sS \
— request POST \
— user “YourApp:secret” \
— data “grant_type=authorization_code” \
— data “code=$AUTH_CODE” \
— data “client_id=YourApp” \
— data “redirect_uri=https://oidcdebugger.com/debug" \
“http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/access_token"

`The result is:

{“access_token”:”IAa…Hh”,”scope”:”openid profile”,”id_token”:”ey…Z7Q”,”token_type”:”Bearer”,”expires_in”:3599}

To check this, get the access token from above and then send it to the “tokeninfo” endpoint.

$ ACCESS_TOKEN=access token as above

$ curl -k -sS http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/tokeninfo?access_token=$ACCESS_TOKEN

The result is:

{“access_token”:”tt…Bb”,”grant_type”:”authorization_code”,”auth_level”:0,”auditTrackingId”:”fb7e6d71–797a-4bae-bf82-b01895d96fca-3865",”openid”:””,”scope”:[“openid”,”profile”],”profile”:””,”realm”:”/YourRealm”,”token_type
“:”Bearer”,”expires_in”:3442,”client_id”:”YourApp”}

Authorization Code Grant with PKCE

This flow is similar to the regular Authorization Code grant type, but the client must generate a code that will be part of the communication between the client and the OpenID provider.

This code mitigates against interception attacks performed by malicious users.

The relying party (the client) must be able to generate a code verifier and a code challenge. For details, see the PKCE standard (RFC 7636).

Essentially, you generate a random string and then hash it, preferably using SHA256.

In the first call you send the hash (the code_challenge) and the hash method (the code_challenge_method).

In the next call, you send the original random string (the code_verifier).

In a private browser session, run:

http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/authorize?client_id=YourApp&response_type=code&scope=openid profile&redirect_uri=https://oidcdebugger.com/debug&code_challenge=j3wKnK2Fa_mc2tgdqa6GtUfCYjdWSA5S23JKTTtPF8Y&code_challenge_method=S256

This brings up the flow as above and returns a code.

Back in the Bash session:

$ AUTH_CODE=nWftY3SXtVGUU4zQ9T9KeLdC5ec

Then:

curl -k -sS \
— request POST \
— user “YourApp:secret” \
— data “grant_type=authorization_code” \
— data “code=$AUTH_CODE” \
— data “client_id=YourApp” \
— data “redirect_uri=https://oidcdebugger.com/debug" \
— data “code_verifier=ZpJiIM_G0SE9WlxzS69Cq0mQh8uyFaeEbILlW8tHs62SmEE6n7Nke0XJGx_F4OduTI4” \
"http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/access_token"

and this brings up the same result as before.

Implicit Grant

In a browser private session:

http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/authorize?client_id=YourApp&response_type=token id_token&scope=openid profile&redirect_uri=https://oidcdebugger.com/debug&nonce=123abc

Then authenticate and consent as above.

Notice that in this flow everything is done in one step. There is no “code” exchange.

OAuth 2.0 Client Credentials Grant

Note that here there is no UI login. Possession of the secret key is taken as proof of authentication. This is ideal for server to server flows.

This also means that authentication is in the context of the application; not the user. The tokens will not contain user-specific information.

As there is no UI requirement, we can use curl.

Back in the Bash session:

curl -k -sS \
— request POST \
— user “YourApp:secret” \
— data “grant_type=client_credentials” \
— data “response_type=token id_token” \
— data “redirect_uri=https://oidcdebugger.com/debug" \
“http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/access_token"

The result is:

{“access_token”:”mo0gm7DvcWWnkOkh5CITd5bT2Wo”,”scope”:”displayName givenName location mail sn uid”,”token_type”:”Bearer”,”expires_in”:3599}

A simplified version more akin to the specification would be:

curl -k -sS \
— request POST \
— data “client_id=YourApp” \
— data “client_secret=secret” \
— data “grant_type=client_credentials” \
“http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/access_token"

But that returns “Invalid authentication method for accessing this endpoint.”.

Hence the use of the “user” parameter.

OAuth 2.0 Resource Owner Password Credentials Grant

In this case, there is no UI and the user name and password are part of the request.

This allows the call to be made in the context of a user.

But putting the credentials “in the clear“ is not ideal!

This is a good flow to use from the perspective of automated testing where you want to test an API that relies on user details.

For this to work, you need to select the authentication method of “client_secret_post”:

Back in the Bash session:

curl -k -sS \
— request POST \
— data “client_id=YourApp” \
— data “client_secret=secret” \
— data “grant_type=password” \
— data “username=YourUser” \
— data “password=password” \
— data “scope=openid profile" \
“http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/access_token"

The result is:

{“access_token”:”8rr-OceYCnQmCjwH99PldtrA0Nc”,”scope”:”openid profile”,”id_token”:”eyJ0…ofQ”,”token_type”:”Bearer”,”expires_in”:3599}

OAuth 2.0 Device Flow

Add “device code” to the grant types.

Back in the Bash session:

curl -k -sS \
— request POST \
— data “response_type=token” \
— data “scope=phone email profile address” \
— data “client_id=YourApp” \
“http://localhost:8060/am/oauth2/realms/root/realms/YourRealm/device/code"

The result is:

{“user_code”:”aPjKa6jz”,”device_code”:”eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYW0v
b2F1dGgyL3JlYWxtcy9yb290L3JlYWxtcy9UZW5uaXMiLCJuYmYiOjE1OTA3MDgyNDAsInVzZXJfY29kZSI6ImFQakthNmp6IiwiaXNzIjoiaHR0cDovL2xv
Y2FsaG9zdDo4MDgwL2FtL29hdXRoMi9yZWFsbXMvcm9vdC9yZWFsbXMvVGVubmlzIiwiZXhwIjoxNTkwNzA4NTQwLCJpYXQiOjE1OTA3MDgyNDAsImp0aSI6
ImQwNjA3OGRlLTFjNDUtNGY4ZC05NmYzLTE4NDcwNGI5M2I2ZCJ9.JdsGOL31X16i0d3v7U2gfGQ46q7p6v-CqduzyhJAmqA”,”interval”:5,”verification_uri”:”http://localhost:8060/am/oauth2/device/user","expires_in":300,"verification_url":"http://localhost:8060/am/oauth2/device/user"}

The device will display a message like:

“In a browser on your phone, go to http://localhost:8060/am/oauth2/device/user and enter the code “aPjKa6jz” “

In a private session, first sign-in.

http://localhost:8060/am/XUI/?realm=YourRealm

Then navigate to the “verification_uri”:

http://localhost:8060/am/oauth2/device/user

You will see:

Click “Allow”.

OAuth 2.1

Just to note that the standard is changing.

Refer: https://oauth.net/2.1/

The major differences from OAuth 2.0 are listed below.

  • The authorization code grant is extended with the functionality from PKCE such that the only method of using the authorization code grant according to this specification requires the addition of the PKCE mechanism
  • Redirect URIs must be compared using exact string matching
  • The Implicit grant (response_type=token) is omitted from this specification
  • The Resource Owner Password Credentials grant is omitted from this specification
  • Bearer token usage omits the use of bearer tokens in the query string of URIs
  • Refresh tokens must either be sender-constrained or one-time use

So for any new applications, consider using Authorisation Code Grant with PKCE for everything except server to server where you should use the client credential flow.

Refer:

https://backstage.forgerock.com/docs/am/6.5/oidc1-guide/#oidc-implementing-flows

https://backstage.forgerock.com/docs/am/6/oauth2-guide/#chap-oauth2-introduction

All good!

--

--

Rory Braybrook
The new control plane

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5