Authorization Code Flow

Overview

The Authorization Code Flow is the most commonly used and recommended OAuth 2.0 authentication flow for server-side and web applications. It provides a secure method for obtaining access tokens by exchanging an authorization code obtained through a user's interaction with an authorization server.

If the application can be trusted to hold a secret (like a web application with secure backend), the standard Authorization Code flow can be used. If the application cannot securely hold a secret (like a web single-page app or mobile app), and particularly if access tokens are needed, it should utilize the Proof Key Code Exchange feature to prevent token hijacking.

If only ID tokens are needed, an alternate login flow (Implicit Flow with Form post) may also be used.

Flow Description

  1. Client Registration: The client application must be registered via the UserClouds Developer Console. During registration, a unique client ID is configured.
  2. Authorization Request: The client initiates the flow by redirecting the user's browser to UserClouds's authorize endpoint.
  3. User Authentication and Consent: The user is prompted to authenticate with the UserClouds authorization server and (if necessary) consent to the requested scopes. After successful authentication and consent, the authorization server generates an authorization code.
  4. Code / Token Exchange:
    1. UserClouds redirects the user's browser back to the client's redirect URI. The client extracts the authorization code from the query parameters and sends an HTTP POST request to UserClouds's token endpoint.
    2. In response, UserClouds provides an access token and (possibly) a refresh token. The client can use the access token to access protect resources on behalf of the user.
  5. Token Refresh (Optional): If a refresh token is obtained during Step 4, the client can use it to request new access tokens without requiring user interaction. This allows the client to maintain user sessions and continue accessing resources after the original token expires.

Step 2 - Authorization Request

Request Example

import "golang.org/x/oauth2"

prov, _ := oidc.NewProvider(context.Background(), "https://sample.tenant.userclouds.com")

oauthConfig := oauth2.Config{
  ClientID:     "5f107e226353791560f93164a09f7e0f",
  Endpoint:     prov.Endpoint(),
  RedirectURL:  "https://contoso.com/callback",
  Scopes:       []string{"openid <ADDITIONAL_SCOPES>"},
}

Without PKCE

GET /oidc/authorize?response_type=code&client_id=5f107e226353791560f93164a09f7e0f&redirect_uri=https%3A%2F%contoso.com%2Fcallback&scope=openid+profile+email&state=n9ftgCrrLNQ7sfxnnFmNcabEn8hFvAypP6Hu625WtBk HTTP/1.1
Host: sample.tenant.userclouds.com
curl 'https://sample.tenant.userclouds.com/oidc/authorize?response_type=code&client_id=5f107e226353791560f93164a09f7e0f&redirect_uri=https%3A%2F%contoso.com%2Fcallback&&scope=openid+profile+email&state=n9ftgCrrLNQ7sfxnnFmNcabEn8hFvAypP6Hu625WtBk'
// Authorization Code Flow without Proof Key Code Exchange (PKCE)
url := oauthConfig.AuthCodeURL("n9ftgCrrLNQ7sfxnnFmNcabEn8hFvAypP6Hu625WtBk")

// Redirect user agent to URL authorize endpoint
http.Redirect(w, r, url, http.StatusTemporaryRedirect)

With PKCE

GET /oidc/authorize?response_type=code&client_id=5f107e226353791560f93164a09f7e0f&redirect_uri=https%3A%2F%contoso.com%2Fcallback&scope=openid+profile+email&state=n9ftgCrrLNQ7sfxnnFmNcabEn8hFvAypP6Hu625WtBk&code_challenge=fJy4Nvl38sFmKyYUMZC1klsg9kn5HKXDUHEdeIuZnyc&code_challenge_method=S256 HTTP/1.1
Host: sample.tenant.userclouds.com
curl 'https://sample.tenant.userclouds.com/oidc/authorize?response_type=code&client_id=5f107e226353791560f93164a09f7e0f&redirect_uri=https%3A%2F%contoso.com%2Fcallback&&scope=openid+profile+email&state=n9ftgCrrLNQ7sfxnnFmNcabEn8hFvAypP6Hu625WtBk&code_challenge=fJy4Nvl38sFmKyYUMZC1klsg9kn5HKXDUHEdeIuZnyc&code_challenge_method=S256'

// Authorization Code Flow with Proof Key Code Exchange (PKCE) using SHA256 (the only supported method)
url := oauthConfig.AuthCodeURL(
  state,
  oauth.SetAuthURLParam("code_challenge_method", "S256"),
  oauth.SetAuthURLParam("code_challenge", "<CODE_CHALLENGE>"))

// Redirect user agent to URL authorize endpoint
http.Redirect(w, r, url, http.StatusTemporaryRedirect)

Because this is a GET request, all parameters should be specified as query parameters in the URL.

ParameterTypeRequired?Description
response_typestringyescode
client_idstringyesThe Client ID of a registered app/client in your UserClouds tenant.
redirect_uristringyesA URL to your application where the user agent will be redirected on completion. Must use https\:// for production web apps, or a mobile custom scheme for mobile apps. Can use http\:// for localhost and development tenants.
scopestringyesMust specify openid in order to get an ID token from the token exchange endpoint later. May also specify profile, email, or other common scopes (see OIDC Spec for details).
statestringyesAn application-defined value used for 2 purpose: encode application state/context for a request, and to prevent CSRF attacks.
code_challenge_methodstringnoIndicates that PKCE should be used with a given method. Only 'S256' (SHA 256) is currently supported.
code_challengestringnoA value generated by applying code_challenge_method (i.e. SHA 256) to a code_verifier value generated by the client. See the PKCE spec for more details. NOTE: this value is required iff code_challenge_method is specified.

Response Example

HTTP/1.1 302 Found

Location: https://contoso.com/callback?code=VieHyGShFqT57%2FjSeqTxQQEraGKhkYK0IMIyFzRRziQKy6G7eMPXDy0s8AiWSwNy&state=n9ftgCrrLNQ7sfxnnFmNcabEn8hFvAypP6Hu625WtBk
HTTP/1.1 302 Found

Location: https://contoso.com/callback?error=invalid_grant&error_description=The+credentials+were+invalid

If the request is valid, the immediate GET call to the authorize endpoint will redirect to Plex's internal endpoint(s) for the user to perform an interactive login. After the user sucessfully authenticates (or in case of an error in the process), the user agent will ultimately be redirected (HTTP 302) to the application-provided (and pre-registered) redirect URL, unless the redirect URL and/or Client ID are invalid (in which case the response will be a 400 Bad Request).

ParameterTypeConditionDescription
codestringsuccessCryptographically secure token which can be exchanged with the Token endpoint.
statestringsuccessThe application-defined value passed in to the Authorize call. The client should validate that this matches to prevent attacks (hijacks, replays, etc.).
errorstringfailureOne of the OAuth/OIDC-spec defined error values. See the OAuth 2.0 spec for more details.
error_descriptionstringfailureA human readable explanation of the error.

Step 4 - Code / Token Exchange

Token Exchange is a key step within the OAuth 2.0 Authorization Code Flow, where the client application exchanges an authorization code for access tokens and optionally a refresh token. To exchange an authorization code for an access and/or ID token, use this code:

POST /oidc/token HTTP/1.1
Host: sample.tenant.userclouds.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=5f107e226353791560f93164a09f7e0f&client_secret=iStpcFf3gfiUjnsbWGFY0C9aWkPzDqT14eyp23ysKuI6iBbOAWcode=VieHyGShFqT57%2FjSeqTxQQEraGKhkYK0IMIyFzRRziQKy6G7eMPXDy0s8AiWSwNy&redirect_uri=https%3A%2F%contoso.com%2Fcallback&code_verifier=iyMU3Af48ZZSPCbJGSxaUGmUJa-6uGiyTq5dwOvuvpg
curl --X POST \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d '{"response_type":"code", "client_id":"5f107e226353791560f93164a09f7e0f", "client_secret":"iStpcFf3gfiUjnsbWGFY0C9aWkPzDqT14eyp23ysKuI6iBbOAW", "code":"VieHyGShFqT57%2FjSeqTxQQEraGKhkYK0IMIyFzRRziQKy6G7eMPXDy0s8AiWSwNy", "redirect_uri":"https://contoso.com/callback" "code_verifier":"iyMU3Af48ZZSPCbJGSxaUGmUJa-6uGiyTq5dwOvuvpg"}'
  'https://sample.tenant.userclouds.com/oidc/token'
import "golang.org/x/oauth2"

prov, _ := oidc.NewProvider(context.Background(), "https://sample.tenant.userclouds.com")

oauthConfig := oauth2.Config{
  ClientID:     "5f107e226353791560f93164a09f7e0f",
  ClientSecret: "iStpcFf3gfiUjnsbWGFY0C9aWkPzDqT14eyp23ysKuI6iBbOAW", // Only specified when NOT using PKCE
  Endpoint:     prov.Endpoint(),
  RedirectURL:  "https://contoso.com/callback",
}

// Authorization Code Flow without Proof Key Code Exchange (PKCE)
tokenResponse, err := oauthConfig.Exchange(ctx, "VieHyGShFqT57%2FjSeqTxQQEraGKhkYK0IMIyFzRRziQKy6G7eMPXDy0s8AiWSwNy")

// -- OR --

// Authorization Code Flow *with* Proof Key Code Exchange (PKCE) using SHA256 (the only supported method)
tokenResponse, err := oauthConfig.Exchange(ctx, "VieHyGShFqT57%2FjSeqTxQQEraGKhkYK0IMIyFzRRziQKy6G7eMPXDy0s8AiWSwNy", oauth.SetAuthURLParam("code_verifier", "iyMU3Af48ZZSPCbJGSxaUGmUJa-6uGiyTq5dwOvuvpg"))

if err { ... }

rawIDToken, ok := tokenResponse.Extra("id_token").(string)
if !ok { ... }

accessToken := tokenResponse.AccessToken

The application uses this endpoint to exchange the code (returned after successful authorization) for a access_token and/or id_token (depending on the authorization flow used).

Request Example

This is a POST request with all parameters form encoded in the body.

ParameterTypeRequired?Description
grant_typestringyesauthorization_code
client_idstringyesThe Client ID of a registered app/client in your UserClouds tenant.
client_secretstringnoThe Client Secret of a registered app/client in your UserClouds tenant. Note that this is used only for the non-PKCE flow.
redirect_uristringyesA URL to your application where the user agent will be redirected on completion. Must use https\:// for production web apps, or a mobile custom scheme for mobile apps. Can use http\:// for localhost and development tenants.
code_verifierstringyesIf PKCE was used to issue the authorization code, then this value must be specified and valid for the token exchange instead of client_secret. See the PKCE spec for more details.

When using PKCE, code_verifier is used instead of client_secret. When using the regular token flow, client_secret is used instead of code_verifier. Do not use both.

Response Example

HTTP/1.1 200 OK
Content-Type: application/json
{
  "access_token":"<ACCESS_TOKEN>",
  "id_token":"<ID_TOKEN>",
  "token_type":"Bearer",
}

If the request is valid, the endpoint returns a json struct with an access token and/or ID token (depending on the scopes of the original request), as well as the token type which is always "Bearer".

ParameterTypeConditionDescription
access_tokenstringsuccessCryptographically secure token which can be used to make API calls.
id_tokenstringsuccessA raw three-part JWT which contains information about the resource owner / user.
token_typestringsuccessAlways "Bearer"

Step 5 - Refresh Token

Request Example

POST /oauth/token HTTP/1.1
Host: sample.tenant.userclouds.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=your_refresh_token&client_id=your_client_id&client_secret=your_client_secret

Response Example

HTTP/1.1 200 OK
Content-Type: application/json

{
   "access_token": "new_access_token",
   "token_type": "bearer",
   "expires_in": 3600,
   "refresh_token": "new_refresh_token"
}

Security Considerations

  • Code Exchange: Ensure secure transmission of the authorization code during exchange and use a secure channel to prevent interception.
  • HTTPS Usage: Utilize HTTPS to protect sensitive information during the authorization process and token exchange.
  • Code Verifier: In the case of PKCE (Proof Key for Code Exchange), implement a secure method for generating and verifying the code challenge and verifier.
  • Refresh Token Lifetime: Refresh tokens may have a different lifetime compared to access tokens. Properly manage refresh token expiration.

Summary

The Authorization Code Flow is a robust and secure method for obtaining access tokens by exchanging an authorization code. It's suitable for applications where server-side communication and secure storage of credentials are possible. Adhering to security best practices, such as HTTPS usage and code exchange protection, is essential to ensure the integrity and confidentiality of the flow.