PKCE


Proof Key for Code Exchange (PKCE, RFC 7636) is an extension to the OAuth 2.0 Authorization Code Flow that prevents authorization code interception attacks. PKCE is required by OAuth 2.1 guidance and is strongly recommended for all clients, especially mobile and single-page applications.

PKCE can be used with or without a client secret. Mobile and other public clients can safely perform the flow without a client secret because PKCE ensures only the app instance that initiated the request can complete the token exchange.

This guide covers OAuth 2.0 Authorization Code with PKCE. The flow issues an access token that can be used to retrieve user attributes from the ID.me Attributes API. If your application also needs an id_token (a signed JWT with identity claims) or requires standard OIDC discovery, see the OIDC PKCE guide instead.

How PKCE Works

PKCE adds a dynamically created cryptographic secret to the standard authorization code flow. Before each authorization request, your application generates a random code_verifier and derives a code_challenge from it. The challenge is sent with the authorization request, and the original verifier is sent with the token exchange, allowing ID.me to verify both came from the same client.

OAuth 2.0 + PKCE Data Flow

image

Prerequisites

Before implementing PKCE, ensure you have:

  • A registered ID.me application with a client_id
  • One or more configured redirect_uri values
  • The OAuth 2.0 scopes your application requires
  • Access to the appropriate environment (Sandbox or Production)
Tip

Most OAuth 2.0 client libraries handle PKCE automatically. Check your library’s documentation before implementing the steps below manually.

Step-by-step implementation

1

Discover endpoint URLs

Fetch the OpenID Connect discovery document to get all endpoint URLs dynamically. This document covers both OAuth 2.0 and OIDC flows and exposes authorization_endpoint, token_endpoint, code_challenge_methods_supported, and other configuration fields.

EnvironmentURL
Sandboxhttps://api.idmelabs.com/oidc/.well-known/openid-configuration
Productionhttps://api.id.me/oidc/.well-known/openid-configuration
2

Generate PKCE pair

Generate a random code_verifier — a 43–128 character URL-safe string. Then derive the code_challenge by computing the SHA-256 hash of the verifier and Base64-URL encoding the result without padding. Store the code_verifier securely for use during the token exchange in step 5.

3

Send authorization request

Redirect the user to the ID.me authorization endpoint. Include your application parameters along with the two PKCE-specific parameters.

Parameters

ParameterDescription
client_idYour application’s client identifier
redirect_uriYour registered redirect URI
response_typeMust be code
scopeSpace-separated list of OAuth 2.0 scopes your application needs
stateA random value tied to this session for CSRF protection
code_challengeThe SHA-256 hash of your code_verifier, Base64-URL encoded
code_challenge_methodMust be S256

Unlike the OIDC flow, a nonce parameter is not used here because no id_token is issued. If your application requires identity claims, use the OIDC PKCE flow which includes openid in the scope and returns an id_token.

4

User authorizes access

The user is redirected to ID.me to authenticate and grant your application the requested scopes. No action is required from your application during this step.

5

Receive authorization code

ID.me redirects the user back to your redirect_uri with an authorization code and the state value appended as query parameters.

Critical

Always verify that the returned state matches the value you sent in the authorization request before proceeding. This prevents CSRF attacks.

6

Exchange code for tokens

Send a POST request to ID.me’s token endpoint with the code_verifier to complete the exchange. ID.me verifies that the code_verifier matches the code_challenge sent in step 3, confirming the request originated from the same client.

  • Endpoint: https://api.id.me/oauth/token
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded

Parameters

ParameterDescription
grant_typeMust be authorization_code
codeThe authorization code from the callback
client_idYour application’s client identifier
redirect_uriMust match the value used in the authorization request
code_verifierThe original code_verifier generated in step 2
client_secretOptional; include if your application can store it securely

Token response

A successful response returns a JSON object containing:

FieldDescription
access_tokenBearer token used to authenticate requests to ID.me APIs
token_typeAlways Bearer
expires_inLifetime of the access token in seconds
refresh_tokenToken used to obtain a new access token without user interaction
scopeThe scopes actually granted (may differ from those requested)
7

Retrieve user attributes

Send a GET request to the ID.me Attributes API with the access token to retrieve the verified user attributes granted by the requested scopes.

  • Endpoint: https://api.id.me/api/public/v3/attributes.json
  • Method: GET
  • Authorization: Bearer {access_token}

The response is a JSON object containing an attributes array. Each element includes a handle (machine-readable key), a name (human-readable label), and a value.

Example
1{
2 "attributes": [
3 { "handle": "fname", "name": "First Name", "value": "Casey" },
4 { "handle": "lname", "name": "Last Name", "value": "Jones" },
5 { "handle": "email", "name": "Email", "value": "casey@example.com" },
6 { "handle": "birth_date", "name": "Birth Date", "value": "1990-09-21" },
7 { "handle": "uuid", "name": "Unique Identifier", "value": "d733a89e2e634f04" }
8 ]
9}

The attributes returned depend on the scopes your application requested and the user’s verified data. If your application requires identity claims in standard OIDC format or a signed id_token, use the OIDC PKCE flow instead.

8

Refresh the access token

When the access token expires, use the refresh_token to obtain a new one without requiring user interaction.

  • Endpoint: https://api.id.me/oauth/token
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded

Parameters

ParameterDescription
grant_typeMust be refresh_token
refresh_tokenThe refresh token from the previous token response
client_idYour application’s client identifier
client_secretOptional; include if your application uses one

Refresh tokens may be rotated on each use. Always store the new refresh_token returned in the response and discard the old one.

PKCE for mobile applications

PKCE is especially important for mobile clients where the client secret cannot be stored securely. Because PKCE ensures that only the same app instance that initiated the login request can complete the token exchange, even if the authorization code is intercepted, mobile and other public clients can safely perform the flow without a client secret.

For mobile-specific implementation details, see the Mobile SDK documentation.

When using PKCE without a client secret, omit the client_secret parameter from the token exchange and refresh requests. The code_verifier provides the necessary proof of identity for the initial exchange.

OAuth 2.0 vs OIDC

ID.me supports both OAuth 2.0 and OpenID Connect. Choosing between them depends on what your application needs:

OAuth 2.0 + PKCEOIDC + PKCE
Obtain access token
Refresh access token
Retrieve user attributes (Attributes API)
Receive id_token (signed JWT)
Access UserInfo endpoint (OIDC claims)
Cryptographic identity verification
Required openid scope

Both flows can retrieve user attributes, but through different endpoints and in different formats. OAuth 2.0 returns attributes via ID.me’s Attributes API (/api/public/v3/attributes.json) in a handle/name/value structure. OIDC returns attributes via the UserInfo endpoint in standard claims format and additionally issues a signed id_token for cryptographic identity verification. If you need to verify the user’s identity with a signed assertion, use the OIDC flow.