Skip to main content

Authorization code grant with PKCE (Proof Key for Code Exchange)

This flow is good for applications that cannot securely store a client_secret, such as single-page applications (SPAs), mobile apps, and desktop applications. We require PKCE to enhance the security of the authorization code flow by adding an additional verification step during the token exchange process.

Use Cases:

  • Native Mobile Applications: Clients (iOS, Android apps) that cannot securely store a traditional, static client_secret within their distributed code.
  • Single-Page Applications (SPAs): Client-side JavaScript applications running in a browser that are vulnerable to code inspection, making them unable to secure a static client_secret.
  • Desktop Applications: Applications running on an end-user's machine, which are also considered public clients for security purposes.
  • Confidential Clients (Web Apps with a Backend): Although they can use a client_secret, PKCE is still recommended as an extra layer of defense against certain types of code injection and CSRF attacks.

Key Benefits:

  • Eliminates Client Secret for Public Clients: Authorization code flow with PKCE allows public clients (like SPAs and mobile apps) to securely use the authorization code grant without needing to store a client_secret, which they cannot do safely.
  • Mitigates Cross-Site Request Forgery (CSRF): By dynamically linking the initial authorization request to the final token exchange, PKCE provides strong protection against CSRF, often making the use of the state parameter redundant for this specific purpose.
  • Default Security Standard: PKCE is now mandated in the OAuth 2.0 Security Best Current Practice and the upcoming OAuth 2.1 specification, making it the required flow for modern application development.

Authorization Code Flow with PKCE

Flow Steps in Detail

Step 1: Authorization Request

The client application redirects the user to the authorization endpoint with the following parameters:

Request:

GET /w/{tenant}/oauth/api/v1/authorize?
response_type=code&
client_id=your-client-id&
redirect_uri=https://yourapp.com/callback&
scope=openid+profile+email+patient/*.read&
state=random-state-value&
code_challenge=CHALLENGE&
code_challenge_method=S256 HTTP/1.1
Host: api.haste.health

Parameters:

ParameterRequiredDescription
response_typeYesMust be code for authorization code flow
client_idYesYour application's client identifier
redirect_uriYesWhere to redirect after authorization (must be pre-registered)
scopeYesSpace-delimited list of requested scopes (must include openid for OIDC)
stateRecommendedRandom value to prevent CSRF attacks
code_challengeRequiredPKCE code challenge (SHA-256 hash of code_verifier)
code_challenge_methodRequiredMust be S256 (SHA-256 hash method)
nonceOptionalRandom value included in ID token to prevent replay attacks
promptOptionalnone, login, consent, or select_account
max_ageOptionalMaximum authentication age in seconds

Step 2: User Authentication

The authorization server authenticates the user through:

  1. Login form (username/password, MFA)
  2. Session cookie (if already logged in)
  3. Federated identity provider (SSO)

If required, the authorization server displays a consent screen showing:

  • Application name and description
  • Requested scopes and permissions
  • Option to approve or deny

Step 4: Authorization Code Generation

Upon successful authentication and consent, the authorization server:

  1. Generates a short-lived authorization code (typically 1-10 minutes)
  2. Associates the code with the client, user, and requested scopes
  3. Redirects back to the client's redirect URI

Response:

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?
code=AUTH_CODE_HERE&
state=random-state-value

Step 5: Token Exchange

The SPA exchanges the authorization code for tokens using the code_verifier (no client_secret needed for public clients):

Request:

POST /w/{tenant}/oauth/api/v1/token HTTP/1.1
Host: api.haste.health
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=https://yourapp.com/callback&
client_id=spa-client-id&
code_verifier=ORIGINAL_VERIFIER

Parameters:

ParameterRequiredDescription
grant_typeYesMust be authorization_code
codeYesThe authorization code received from callback
redirect_uriYesMust exactly match the original redirect URI
client_idYesYour SPA's registered client identifier
code_verifierRequiredThe original random string used to generate code_challenge (retrieved from sessionStorage)

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "openid profile email patient/*.read",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Step 6: Access Protected Resources

Use the access token to call protected APIs:

Request:

GET /w/{tenant}/{project}/api/v1/fhir/r4/Patient/123 HTTP/1.1
Host: api.haste.health
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Error Handling

Authorization Errors

Error CodeDescriptionResolution
invalid_requestMissing or invalid required parameterCheck all required parameters are included
unauthorized_clientClient not authorized for this grant typeVerify client configuration
access_deniedUser denied authorization requestInform user and allow retry
unsupported_response_typeAuthorization server doesn't support response typeUse response_type=code
invalid_scopeRequested scope is invalid or unknownCheck scope names and permissions
server_errorAuthorization server errorRetry with exponential backoff
temporarily_unavailableServer temporarily unavailableRetry after delay

Token Exchange Errors

Error CodeDescriptionResolution
invalid_requestMissing required parameterInclude all required parameters
invalid_clientClient authentication failedVerify client_id and client_secret
invalid_grantAuthorization code invalid, expired, or revokedRequest new authorization
unauthorized_clientClient not authorized for authorization code grantUpdate client configuration
unsupported_grant_typeGrant type not supportedUse grant_type=authorization_code

Example Error Response

{
"error": "invalid_grant",
"error_description": "The authorization code has expired or has already been used",
"error_uri": "https://api.haste.health/errors/invalid_grant"
}

Comparison with Other Flows

FeatureAuthorization Code + PKCEClient CredentialsImplicit (Deprecated)
Use CaseSPAs, mobile appsService-to-serviceSPAs (legacy)
Client SecretNot required (PKCE)RequiredNot possible
Refresh TokensYesOptionalNo
SecurityHighestHighLow
User ContextYesNoYes
PKCE Required✅ Yes❌ NoN/A
Recommended✅ Yes✅ Yes❌ No (use Auth Code + PKCE)

Resources