Dance the OAuth with me
OAuth and its cousin OIDC 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 approvePassword
: their passwordHOST
: The starting URL for the first callstate
: a random string, need to stay the same through the sequenceclient_id
: The application configured as service providerclient_secret
: The service provider "password"scope
: the scope (or a subset) you have configured for the service providerredirect_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.
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 authorizationtoken_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 Stephan H Wissel on 06 June 2022 | Comments (1) | categories: WebDevelopment