wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Dance the OAuth with me


OAuth and its cousin OICD are the ubiquitous methods to gain identity and authorization information. Since it is a ménage à trois between a user, an Identity provider (IdP) and an application, refered to as "Service provider", it is hard to trouble shoot.

A play in five acts

In the recent Project KEEP we build an IdP into the API, so you have the choice of just using Domino or using an external IdP.

To ensure it works as expected several dependent HTTPS calls were needed. Each call would harvest some information into environment variables for the following step

Act 0 - initial setup

Store several variables into the environment:

  • UserName: the user you will simulate to approve
  • Password: their password
  • HOST: The starting URL for the first call
  • state: a random string, need to stay the same through the sequence
  • client_id: The application configured as service provider
  • client_secret: The service provider "password"
  • scope: the scope (or a subset) you have configured for the service provider
  • redirect_uri: one of the redirection URIs you have configured for the service provider

An OAuth flow contains basic authentication calls, so you need to ensure proper TLS connections.

OAuth Dance

Act 1 - lay of the land

Establish the end-points you have to deal with:

curl --location --request GET "$HOST/.well-known/openid-configuration"

Harvest from the JSON response (JQ is your friend):

  • authorization_endpoint: URL for the service provider to request authorization
  • token_endpoint: URL where authorization can be exchanged for access tokens

Act 2 - ask nicely

This call initiates the flow and ends with a 302 status code, sending you to the UI part for granting or revoking access

curl --location -g --request GET "$authorization_endpoint?client_id=$client_id&response_type=code&state=$state&scope=$scope&redirect_uri=$redirect_uri"

There won't be a body result, but a Location header, you can grab and use in a GET request to retrieve the consent UI. This part in IdP specific, so using a different IdP will change act 3.

Act 3 - authenticate and consent

Two calls are required. First authenticate with KEEP, then consent for the application to have access:

curl --location --request POST "$HOST/api/v1/authforoauthflow" \
--header "Content-Type: application/json" \
--data-raw "{'password' : \"$Password\",'username' : \"$UserName\",'scopes' : 'oauth'}"

Harvest the bearer response from the JSON body. The publish you consent. This one is a classic HTML form post, so the header needs to be application/x-www-form-urlencoded

curl --location -g --request POST '{{authorization_endpoint}}/decision' \
--header "Authorization: Bearer $bearer" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'response_type=code' \
--data-urlencode "scope=$scope" \
--data-urlencode "state=$state" \
--data-urlencode "client_id=$client_id" \
--data-urlencode "redirect_uri=$redirect_uri" \
--data-urlencode 'decision=allow'

The JSON result will have only two values: state and authorization_code. Capture the authorization_code.

Act 4 - gain access

The authorization_code has a short livespan, so it needs to get exchanged for an access token and a refresh token. This call works once. A second call will fail without Act 3 being repeated. This call again is a classic HTML form post.

curl --location -g --request POST "$token_endpoint" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "code=$authorization_code" \
--data-urlencode "redirect_uri=$redirect_uri" \
--data-urlencode "client_id=$client_id" \
--data-urlencode "client_secret=$client_secret"

Note In bash environment variables only get resolved in double quotes, hence the mixed use of single and double quotes here.

The JSON result will contain the access_token, which is your KEEP compliant, short lived JWT and the refresh_token to get a new access token without the need for user interaction. To verify that it is working call this:

curl --location -g --request POST "$token_endpoint}}" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode "refresh_token=$app_refresh_token" \
--data-urlencode "scope=$scope" \
--data-urlencode "client_id=$client_id" \
--data-urlencode "client_secret=$client_secret"

Again the access_token is your KEEP JWT. The call will not issue a new refresh token.

Epilogue

In just five acts, you can manually retrace the steps the OAuth dance performs to gain access. There's a variation to it: instead of client_id and client_secret being posted in the request body, they can be supplied as basic authentication header:

curl -p "$client_id:$client_secret" ...

As usual YMMV


Posted by on 06 June 2022 | Comments (0) | categories: WebDevelopment

Comments

  1. No comments yet, be the first to comment