Code Flow

Code Flow

operate

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 in the "Setup and Getting Started" section and the "Setup a Username Authenticator". 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

Code Flow First Part

  1. Browser redirects to the Authorize endpoint of the OAuth Server
  2. If the user isn't authenticated the OAuth Server redirect to the Authentication Service. Note that these two entities, while running in the same product, are separate conceptually.
  3. The User authenticates, and is redirected back to the OAuth Server
  4. The OAuth Server issues a one time token called an Authorization Code

Token Endpoint

Code Flow Second Part

  1. The Client backend makes a POST request to the Token endpoint with the Authorization Code and Client Credentials
  2. The OAuth 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 standard.

Setup in Curity

Visit the Profiles screen and click the Token Service. On the right select Clients and click New.

New Client

Give the client an ID (eg. www for a website client).

New Client

Capabilities

Scroll down to the Capabilities section and click Add capabilities.

Capabilities

Select the Code Flow capability and click Next.

Code Flow

Redirect URL

The redirect URL 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.

Redirect URL

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).

Secret

User Authentication

For user authentication select the authenticator created in the authenticator tutorial.

User Authentication

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.

Add Scope

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=www

If 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 of authorization_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:

  1. In the form-URL-encoded request body together with the other parameters. In this case, the parameters client_id and client_secret should be used.
  2. In an Authorization request header using the HTTP basic mechanism1.

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:

Refresh Token Toggle

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:

https://localhost:8443/oauth/v2/oauth-authorize?response_type=code&client_id=www&scope=openid&redirect_uri=https://localhost/callback

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 the ones on the developer portal. In particular, the Python example 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:

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.

Was this page helpful?