OAuth 2.0 Authorization Code Flow w/ PKCE

This documentation outlines the OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange). The Authorization Code Flow with PKCE ensures secure authorization for users and applications

Overview

What is OAuth 2.0?

OAuth 2.0 is an open standard for authorization that allows third-party applications secure access to user data stored on another service without sharing the user's password. Instead of credentials, the third-party app gets an access token which it can use to make requests on behalf of the user. OAuth has multiple flows, however the Developer Hub only supports the OAuth 2.0 Authorization Code Flow with PKCE, which is widely used by web apps, mobile apps, and APIs.

What is PKCE?

PKCE, which stands for Proof Key for Code Exchange, is a security enhancement of the OAuth 2.0 Authorization Code Flow, especially when the client is a public client (such as mobile apps and single-page web applications) that cannot safely store a client secret. PKCE adds an additional layer of security by using a code challenge and code verifier. When the authorization code is sent, the client proves that it was the original requester by sending the correct code verifier. This prevents attackers from intercepting and using the authorization code to gain access to the tokens.



Quick Start

Prerequisites

Before implementing OAuth 2.0, you must have:

  • Your client credentials (client_id and client_secret)

  • An HTTPS-enabled redirect URI for your application

  • Understanding of the basic OAuth 2.0 concepts outlined below

Flow Overview

  1. Preparation: Generate PKCE credentials

  2. Authorization: Request authorization code from user

  3. Token Exchange: Exchange authorization code for access token

  4. Token Management: Refresh tokens when they expire


Implementation Guide

Phase 1: Preparation

Generate PKCE Credentials

To secure the authorization process, the client application generates a code verifier and code challenge.

Code Verifier:

The client generates a code verifier, which is a cryptographically random string (typically 43-128 characters long). This code verifier is kept secret and is never transmitted over the network.

Code Challenge:

The client creates a code challenge by applying SHA-256 hashing algorithm to the code verifier, and then encoding the result using Base64 URL encoding. The code challenge is what the authorization server will use to verify that the client requesting the token is the same one that initiated the authorization flow.

Sample Code for Generating Code Verifier and Code Challenge in C#:


Phase 2: Authorization

Request Authorization Code

To initiate the Authorization flow, the client application redirects the user to the Authorization Server's /oauth/authorize endpoint with the necessary parameters.

Authorization Server base URLs:

Environment
Base URL

Development

https://auth.rdbranch.com

Production

https://auth.resdiary.com

Authorization Request URL:

Parameters:

Parameter
Required
Type
Description
Validation

response_type

Yes

string

Should be code, which tells the authorization server that the client expects an authorization code.

Must be "code"

client_id

Yes

string

Your application's client ID

Must match registered app

redirect_uri

Yes

string (URL)

The URI to which the user will be redirected after authorization. Should match the Redirect URI configured in your App's Settings.

Must match configured URI exactly

scope

Yes

string

A space-separated list of permissions being requested. Depending on the target API of your app, it can be consumer_api, data_extract_api or epos_api. It's also recommended to add the offline_access scope to enable your app to refresh access tokens.

One of: consumer_api, data_extract_api, epos_api

code_challenge

Yes

string (43-128 chars)

The hashed version of the code verifier.

Base64 URL-encoded SHA-256 hash

code_challenge_method

Yes

string

Should be S256. This indicates that the client is using SHA-256 to generate the code challenge.

Must be "S256"

microsite_name

Yes

string

The microsite name of the Diary resource requested for authorization.

Valid microsite name

state

Recommended

string

A random string to prevent CSRF attacks. This value is returned by the authorization server along with the authorization code.

Cryptographically random, validate on return

Best Practice: Always include the state parameter to prevent CSRF attacks. Generate a cryptographically random value, store it in your session, and validate it when the authorization code returns.

Understanding Scopes

Scopes define the level of access your application requests from the user. Each scope grants specific permissions:

Scope
Permissions
Use Case

consumer_api

Read/write consumer bookings and related data

Booking management applications

data_extract_api

Read analytics and reporting data

Reporting and analytics tools

epos_api

POS integration capabilities

Restaurant POS systems

offline_access

Enables refresh token issuance

Long-running applications that need ongoing access

💡 Tip: Request only the scopes your application needs. Users are more likely to grant authorization when they understand what access is being requested.

Example Authorization Request:

Handle User Consent

After redirecting the user to the authorization endpoint:

  1. If the user is already logged in to ResDiary, they will be redirected straight to the consent form

  2. If not logged in, the user will first authenticate with ResDiary

  3. The user reviews and grants permission to your app

Receive Authorization Code

Once the user has granted access to the requested resource, the authorization server redirects the user back to your redirect_uri with the authorization code. If a state parameter was provided in the authorization request, it will also be included and must be validated.

Example Redirect (after user grants permission):

⚠️ Security Warning: Always validate that the state parameter matches the value you sent to prevent CSRF attacks.


Phase 3: Token Exchange

Exchange Authorization Code for Access Token

The client application will exchange the authorization code together with the code verifier for an access token by making a POST request to the /oauth/token of the Authorization server.

Parameters:

Parameter
Required
Type
Description
Validation

grant_type

Yes

string

Specifies the type of OAuth 2.0 grant being used. For this flow, it must be authorization_code.

Must be "authorization_code"

client_id

Yes

string

The unique identifier for your application (client). This is provided when you register your app.

Must match registered app

client_secret

Yes

string

The secret key used by your application to authenticate with the authorization server.

Keep confidential

redirect_uri

Yes

string (URL)

The URI where the user is redirected after they authorize your application.

Must match authorization request exactly

code

Yes

string

The authorization code received from the authorization server after the user grants permission.

Must be unused and unexpired

code_verifier

Yes

string (43-128 chars)

The original code_verifier used in the PKCE flow, which should match the one used during the initial authorization request.

Must match code_challenge

💡 Tip: Authorization codes are short-lived (typically 10 minutes). Exchange them for tokens promptly after receiving them.

Token Response

After the authorization server verifies the code challenge against the code verifier, it responds with the token response.

Example Token Response

Token Response Fields:

  • access_token: Used to authenticate API requests on behalf of the user

  • token_type: Always "Bearer" for OAuth 2.0

  • expires_in: Token lifetime in seconds (typically 3600 = 1 hour)

  • scope: Granted permissions (may differ from requested if user denied some)

  • refresh_token: Used to obtain new access tokens when the current one expires (only included if offline_access scope was requested)

Use Access Token for API Calls

Include the access token in the Authorization header of your API requests:

The ResDiary API will validate the token and respond with the requested resource.


Phase 4: Token Management

Refresh Access Tokens

Access tokens issued by the Authorization Server are short-lived (typically 1 hour). To refresh your access tokens before they expire, send a POST request to the /oauth/token endpoint of the Authorization Server.

Parameters:

Parameter
Required
Type
Description
Validation

grant_type

Yes

string

Specifies the type of OAuth 2.0 grant being used. For the refresh token flow, it must be refresh_token.

Must be "refresh_token"

client_id

Yes

string

The unique identifier for your application. This is provided when you register your app.

Must match registered app

client_secret

Yes

string

The secret key used by your application to authenticate with the authorization server.

Keep confidential

refresh_token

Yes

string

The refresh token that was issued alongside the access token. This token is used to request a new access token.

Must be valid and unexpired

Best Practice: Refresh access tokens proactively before they expire (e.g., when 80% of their lifetime has passed) rather than waiting for API calls to fail.

Handle Token Expiration

When an access token expires, API requests will return a 401 Unauthorized error. Your application should:

  1. Detect the 401 error response

  2. Use the refresh token to obtain a new access token

  3. Retry the original API request with the new access token

  4. If refresh token is also expired, redirect user to re-authorize

Token Storage Best Practices

⚠️ Security Warning: Never store refresh tokens in browser localStorage or sessionStorage. These are vulnerable to XSS attacks.

Recommended storage:

  • Access tokens: Memory or sessionStorage (acceptable risk due to short lifetime)

  • Refresh tokens: Secure HTTP-only cookies or secure backend storage

  • Code verifier: Temporary session storage, clear after token exchange


Security Best Practices

HTTPS Requirement

⚠️ Critical: All OAuth 2.0 flows MUST use HTTPS for all redirect URIs and API endpoints. Never use HTTP in production.

Using HTTPS ensures that:

  • Authorization codes cannot be intercepted

  • Tokens are transmitted securely

  • Client secrets remain confidential

CSRF Protection

Best Practice: Always include and validate the state parameter to prevent CSRF attacks.

The state parameter should be:

  • Cryptographically random (at least 128 bits of entropy)

  • Stored in your session before redirecting to the authorization endpoint

  • Validated when the authorization code returns to your redirect URI

  • Used only once to prevent replay attacks

Example implementation:

Client Secret Handling

⚠️ Security Warning: Never expose your client_secret in client-side code (JavaScript, mobile apps).

Best practices:

  • Store client secrets securely on your backend server

  • Use environment variables or secure configuration management

  • Never hardcode secrets in source code

  • For public clients (SPAs, mobile apps), use PKCE without client_secret

  • Rotate client secrets periodically

Token Storage Security

Never store tokens in:

  • Browser localStorage (vulnerable to XSS)

  • Browser sessionStorage for refresh tokens

  • Plain text files

  • Version control systems

Recommended approaches:

  • Use secure HTTP-only cookies for refresh tokens

  • Store tokens in backend session storage

  • Use encrypted storage on mobile devices

  • Clear tokens on logout

Additional Security Recommendations

  • Implement token refresh before expiration (proactive, not reactive)

  • Set appropriate token lifetimes based on security requirements

  • Monitor for suspicious OAuth activity (multiple failed attempts)

  • Validate all redirect URIs strictly (no wildcards)

  • Keep PKCE implementation libraries up to date

  • Use strong random number generators for all cryptographic values


Error Handling

Common Error Codes

Authorization Errors

Error Code
Description
Solution

invalid_request

Missing or malformed parameters

Verify all required parameters are included and properly formatted

unauthorized_client

Client not authorized for this grant type

Check that your app is properly registered and approved

access_denied

User denied authorization

Handle gracefully, allow user to retry, explain why permissions are needed

unsupported_response_type

Invalid response_type parameter

Must be "code" for authorization code flow

invalid_scope

Invalid or unsupported scope

Use only supported scopes: consumer_api, data_extract_api, epos_api, offline_access

server_error

Authorization server encountered an error

Retry after delay, contact support if issue persists

temporarily_unavailable

Authorization server temporarily unavailable

Retry with exponential backoff

Token Exchange Errors

Error Code
Description
Solution

invalid_grant

Authorization code invalid, expired, or already used

Codes expire quickly (typically 10 minutes), ensure prompt exchange. Each code can only be used once.

invalid_client

Client authentication failed

Verify client_id and client_secret are correct

invalid_request

Malformed token request

Check all required parameters are included

unauthorized_client

Client not authorized

Verify app is properly configured

unsupported_grant_type

Invalid grant_type parameter

Must be "authorization_code" or "refresh_token"

Refresh Token Errors

Error Code
Description
Solution

invalid_grant

Refresh token invalid or expired

User must re-authorize to obtain new refresh token

invalid_client

Client authentication failed

Verify client credentials

Troubleshooting Guide

"Invalid code_verifier" Error

Possible causes:

  • Code verifier doesn't match the code challenge sent in authorization request

  • Code verifier was modified during storage or transmission

  • Incorrect SHA-256 hashing or Base64 URL encoding

Solutions:

  1. Ensure code_verifier is stored exactly as generated (43-128 characters)

  2. Verify code_challenge was correctly computed (SHA-256 hash, then Base64 URL-encoded)

  3. Check that no whitespace or encoding issues occurred

  4. Ensure code_verifier hasn't been truncated or modified

"Redirect URI Mismatch" Error

Possible causes:

  • Redirect URI in token request doesn't exactly match authorization request

  • Redirect URI doesn't match what's configured in app settings

  • Trailing slashes, query parameters, or protocol differences

Solutions:

  1. Ensure redirect_uri in token request exactly matches authorization request

  2. Verify redirect URI matches your app configuration

  3. Check for trailing slashes (with vs without must match)

  4. Ensure protocol matches (http vs https)

  5. Remove any query parameters unless they were in the original configuration

"Token Expired" Error (401 Unauthorized)

Possible causes:

  • Access token has exceeded its lifetime (typically 1 hour)

  • Clock skew between client and server

  • Token was revoked

Solutions:

  1. Implement token refresh before expiration (proactive approach)

  2. Use refresh token to obtain new access token

  3. Retry failed API request with new access token

  4. If refresh token also expired, redirect user to re-authorize

"State Parameter Mismatch" Error

Possible causes:

  • State parameter returned doesn't match what was sent

  • State was not stored correctly in session

  • Session expired or was cleared

  • User opened multiple authorization flows simultaneously

Solutions:

  1. Verify state parameter is stored correctly in session before redirect

  2. Ensure state is validated when authorization code returns

  3. Check for session expiration or loss

  4. Implement proper session management for concurrent flows

"Invalid Scope" Error

Possible causes:

  • Requested scope is not supported

  • Requested scope is not enabled for your app

  • Typo in scope name

Solutions:

  1. Use only supported scopes: consumer_api, data_extract_api, epos_api, offline_access

  2. Verify your app is configured to request these scopes

  3. Check for typos in scope names (case-sensitive)

  4. Ensure scopes are space-separated in the request

Last updated

Was this helpful?