
Code Flow
Using the OAuth 2 Code Flow
OAuth 2 consists of many different types of flows. Many of those have various permutations and extensions that can be used. The most popular of all these is the code flow. This is a message exchange pattern used by web-based applications. These apps are ones that have a back-end or use a traditional approach to web application development. Because such applications reside on a server, it is possible to issue this client a secret or other kind of credential which it can keep safe. Such clients are referred to as "private" ones.
In this tutorial, we'll explain how this flow works and how to configure it in the Curity Identity Server. We'll also explain the common extensions that can be made to this flow and discuss when and why you may use them.
Pre-requisites
This tutorial builds on the configuration setup described in the "First Configuration" and the "Configure an Authenticator" steps under the menu "Getting Started". If you haven't done those steps yet you can visit those guides here:
It's possible to run this tutorial on a custom setup also, but the URLs may be different, as well as the capabilities configured in the profiles.
Overview
To start, the basics of this flow are shown in the following figures:
Authorization Endpoint
- Browser redirects to the Authorize endpoint of the Authorization Server
- If the user isn't authenticated the Authorization Server redirect to the Authentication Service. Note that these two entities, while running in the same product, are separate conceptually.
- The User authenticates, and is redirected back to the Authorization Server
- The Authorization Server issues a one time token called an Authorization Code
Token Endpoint
- The Client backend makes a POST request to the Token endpoint with the Authorization Code and Client Credentials
- The Authorization Server validates the code and the credentials, and returns an access token and optionally a refresh token if configured on the client.
The last point about the inclusion of a Refresh Token is an important one. This extra token allows a client to extend the user's session for a prolonged period of time without troubling the user to authenticate again. This is often desirable and one of the primary reasons that the code flow would be used instead of the implicit flow or other exchange where the client is a public one (i.e., one that cannot keep a secret).
To learn more about the client parameters of the Code flow see OAuth Code Flow.
RFC Reference
For reference, this flow is specified in section 4.1 of the OAuth standardSetup in Curity
Visit the Profiles screen and click the Token Service. On the left select Clients and click New.
New Client
Give the client an ID (eg. www
for a website client).
Capabilities
Scroll down to the Capabilities section and click Add capabilities.
Select the Code Flow capability and click Next.
Redirect URL
The redirect URI is back at the client. If you don't know what you will use just enter http://localhost/callback
for now. This can be changed later.
This tutorial will manually run the flow so localhost is fine. If you plan to use OAuth.tools for testing purposes click the Add Redirect URIs
button to add the callback for oauth.tools.
Client Authentication
For client authentication select secret
and enter a secret. Make sure to remember it since it cannot be retrieved later again (but can be reset).
User Authentication
For user authentication select the authenticator created in the authenticator tutorial.
Add the openid Scope
We will also run the OpenID Code flow, so add the openid
scope to the client by scrolling down to the Permissions section of the client.
Commit
Make sure to remember to commit the changes in the Changes -> Commit menu.
Requesting an Authorization Code
Once the client is configured we can request the authorization code. Where you make this to depends on the location of the authorization endpoint but if you're using the setup wizard the flow starts at:
https://localhost:8443/oauth/v2/oauth-authorize
The parameters that can be sent to this endpoint vary, but at least response_type
with a value of code
and client_id
must be provided. state
should be included and so should a redirect_uri
. If the client has defined multiple redirect endpoints, then the redirect_uri
must be included. If OpenID Connect is to be used, then a scope
parameter must be included with at least openid
. This scope should be used when the client needs an ID Token.
See OAuth Code Flow for details about the OAuth related parameters.
An example of the minimum required inputs is a URL such as this:
https://localhost:8443/oauth/v2/oauth-authorize?response_type=code&client_id=wwwIf things are setup correctly, then the user should be presented with a login form. If this is a first use and no accounts are created, you can click Create Account, and register a new account.
After logging in, the user's browser will be redirected to the redirect URI of the client application. On this query string will be a code
query string parameter. If the state
was included in the original request, it will also be included on the query string. A sample response will look like this:
https://localhost/callback?code=YJBtNPyndBUH7ddx5AsF9HAnTgYtnV9l
The client should make no assumptions about the status code used to redirect the user's browser, nor that there will even be a redirect. The standard allows this change of control to be made using any method understood by the user agent, and that is the "API" between the OAuth server and the client -- nothing else.
Including a State
It is important to include a state
query string parameter in the original request. This is technically optional according to the OAuth specification, but highly recommended. If it is included though, it should be verified when the callback is received. The way in which it is created and verified varies, but an easy way is to symmetrically encrypt a value, send the cipher-text, and verify that it can be decrypted later. The value could be the client ID, the date, etc. The point of the state is to verify that that the request originated from the client. A request that includes a state could look something like this:
https://localhost:8443/oauth/v2/oauth-authorize?response_type=code&client_id=www&state=9dDZSJvwRrnJS8YFLK0NckPkEwvgKQKeG4LA06yV%2BftWGq%2FxlYRdCw%3D%3D
Redeeming the Code
With the code in hand and the state verified (if applicable), the client app can redeem it for an Access Token and possibly other tokens -- like a Refresh Token and ID Token. To do this, the client app must make an HTTP POST request to the OAuth server's token endpoint -- step 5 in the figure above. The body of this request must include the following:
- A
grant_type
parameter with a value ofauthorization_code
- A
code
parameter where the value is the authorization code plucked off of the callback request - A
redirect_uri
if it was sent in the request to the authorization endpoint
All of these must be form URL encoded; JSON isn't allowed.
This call is authenticated; the client must prove its identity by authenticating. There are various ways that this can be done, but the most common is to use the client ID together with a secret. The ID must be the same one that used in the request to the OAuth server's authorization endpoint (step one above). The ID and secret can be sent in either of two ways:
- In the form-URL-encoded request body together with the other parameters. In this case, the parameters
client_id
andclient_secret
should be used. - In an
Authorization
request header using the HTTP basic mechanism.[1]
If the client includes its credentials in both of these locations, they must match; otherwise, Curity will reject the call. A sample HTTP request is shown in the following listing (which includes line breaks for readability's sake):
POST /oauth/v2/oauth-token HTTP/1.1
Host: localhost:8443
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=xPwTedMCpix4I14rYlrlkXWYnBOxEv4G&
client_id=www&
client_secret=the_secret&
redirect_uri=https%3A%2F%2Flocalhost%2Fcallback
Redirect URI
This request will be rejected if the original request did not include the redirect_uri
. A redirect_uri
should only be sent to the token endpoint if it was also sent in the authorization request.
The response will be JSON. If things go well, it will contain at least an Access Token. If so configured, the response will also include a Refresh Token. If the client provided the openid
scope in the authorization request, the response will also include an ID Token. The following is a response that only includes an Access Token and a Refresh Token:
{
"access_token": "7efda4da-e316-452c-88a6-4cd2482a6f00",
"refresh_token": "d392c88c-5080-451b-a551-c5119e98195c",
"scope": "",
"token_type": "bearer",
"expires_in": 299
}
This response does not include a scope value. This is because the authorization request also did not include any.
Including or Excluding a Refresh Token
To disable a Refresh Token from being sent to the client, in the admin UI, go to the client (which is accessible from the Clients
menu of the Token profile). Toggle off the Refresh Token Time to Live
option. This can be toggled on and an expiration time for the Refresh Token can be set:
When this is done, a response will look more like this:
{
"access_token": "9c6d9647-d100-49cc-8c1f-66a96a305c90",
"scope": "",
"token_type": "bearer",
"expires_in": 300
}
Refresh Token
The inclusion of the Refresh Token. This is Curity's default behavior for a client that supports the code flow.Including an ID Token
If the client includes the openid
scope in the original request, like this:
OpenID Connect Request
When requesting an ID Token using the openid scope, the redirect_uri
is required according to the OpenID Connect specification. Omitting the `redirect_uri will result in a bad request.
Then, the response from the token endpoint will look more like this:
{
"id_token": "eyJraWQiOiItMzgwNzQ4MTIiLCJ4NXQiOiJNUi1wR1RhODY2UmRaTGpONlZ3cmZheTkwN2ciLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiNEt1SXFOUmFsWWFlZUFYZnlSM21TdyIsImFjciI6InVybjpzZTpjdXJpdHk6YXV0aGVudGljYXRpb246aHRtbC1mb3JtOmh0bWwxIiwiYXpwIjoid3d3IiwiYXV0aF90aW1lIjoxNTIwMzIxOTEyLCJleHAiOjE1MjAzMjYzNzQsIm5iZiI6MTUyMDMyMjc3NCwianRpIjoiZmI2MGZiNDYtNGYwZS00NmI2LTkyNzQtYzJlZDBhMzE0MDU2IiwiaXNzIjoiaHR0cHM6Ly9zcHJ1Y2U6ODQ0My9-IiwiYXVkIjoid3d3Iiwic3ViIjoiam9obmRvZSIsImlhdCI6MTUyMDMyMjc3NCwicHVycG9zZSI6ImlkIn0.D_vHKt1rRwqIXX5VumzFkweiTKWykx7X6Wv7LLYSgAgNoq67ews6PoLlWTnviMNSYXhPV4xpsEqt4b-lMdG53I8g_tslrxVOI3FOy5mysZIub74wkkE0J6Qgba3s8DlbWhj9h4zIO3MNkhfdURJ2PJ6GY6kwc_8Eril0ilNZ8TU_puT8bQHJ_QWxghY3XpeQHtCyzuVDgVv6q7gfcGoy1JxZaLoXNSh02ZIpp7thVrgEAWAiWo7v46HJFiBNpyPnJfzDRwbTIdPFMEKoHOLjUCczsii_4akCb97IVPz5I3bRWASTyig7P_Q0646cNHsHZM-pan7cl5bYb42JI0ykCw",
"token_type": "bearer",
"access_token": "858a3746-ebbd-473c-9af7-f74bedd114c5",
"refresh_token": "3aba4d9b-6339-442e-9c05-126d9cce29da",
"scope": "openid",
"expires_in": 299
}
The id_token
is a JSON Web Token (JWT). The contents of the payload will include information about the user and the authentication method that was used (the "ACR"):
{
"at_hash": "4KuIqNRalYaeeAXfyR3mSw",
"acr": "urn:se:curity:authentication:html-form:username-password",
"azp": "www",
"auth_time": 1520321912,
"exp": 1520326374,
"nbf": 1520322774,
"jti": "fb60fb46-4f0e-46b6-9274-c2ed0a314056",
"iss": "https://spruce:8443/~",
"aud": "www",
"sub": "johndoe",
"iat": 1520322774,
"purpose": "id"
}
This token is intended for the client app whereas the Access Token is for the API and the Refresh Token is to be used with the OAuth server. For more details about how to validate an ID Token see Validating an ID Token
Testing
Testing the code flow can be done using a browser to begin with and then some HTTP client, like curl
or Postman to redeem the authorization code. This is a very helpful way to see exactly what is going on. It can also be challenging and certainly doesn't work well for demos :-)
Instead we suggest to use the online tool OAuth.tools which is a powerful tool to explore OAuth and OpenID Connect.
Some alternatives that you may want to try are in particular the Python example. It is used a lot and very well maintained.
More Variations and Info
In this tutorial, we talked about different variations on this flow that effect the kinds of tokens that are issued, the scope of access that is granted, etc. Some other variations are covered in related tutorials:
- Enabling interactive user consent
- Using Proof Key for Code Exchange (PKCE)
- Using Pairwise Pseudonymous Identifiers (PPIDs)
If you have questions on anything in this tutorial, don't hesitate to contact us.
1 OAuth allows for other HTTP authentication methods, like digest, to be used; Curity does not support these, but does allow authentication to be performed using the JWT grant type in addition to the client secret in a form post and basic authentication. ↩