Authorization Code Flow with Proof Key for Code Exchange (PKCE)
This flow corresponds to the Authorization Code Flow, extended by a security component, described in the RFC7636.
The Problem PKCE Solves: Traditional OAuth flows can be vulnerable when used by public clients (like mobile apps or single-page applications) because they can’t securely store client secrets.
How PKCE Works:
- Code Verifier: The client generates a random, cryptographically secure string
- Code Challenge: The client creates a hashed version of the code verifier
- Authorization Request: The client sends the code challenge (not the verifier) to the authorization server
- Token Exchange: When exchanging the authorization code for tokens, the client must provide the original code verifier
- Verification: The server verifies that the code verifier matches the previously sent code challenge
In Everyday Terms: Think of it like a secret handshake - the client creates a secret phrase (code verifier), tells the server a scrambled version of it (code challenge), and later proves they’re legitimate by providing the original unscrambled phrase.
It needs to be started in a web browser since the flow will redirect requests several times to the ezeep or even external ID providers that implement some interactive web application.
Starting Point of Authorization Flow
Client generates a random code_verifier
string and calculates the code_challenge
value by using the SHA256 hashing algorithm.
To initiate the authentication the application has to start at the /oauth/authorize
page with the following query parameters:
Attribute | Type | required | description |
---|---|---|---|
response_type |
string | Yes | defines the OAuth2 grant, needs to be code
|
client_id |
guid | Yes | the Client ID you received from ezeep |
redirect_uri |
string | Yes | Must match one of the redirect URIs you provided when you requested your Client ID |
code_challenge |
string | Yes | A cryptographically derived value from the code_verifier that’s sent to the authorization server |
code_challenge_method |
string | Yes | Specifies how the code_challenge was derived from the code_verifier, set to ‘S256’ |
social |
string | No |
azure to automatically redirect to Microsoft for authentication |
prompt |
string | No |
none to prevent Microsoft from showing the account selection prompt, for direct selction of login provider: azure for Microsoft |
scope |
string | No |
printing (space sperated scope list) |
state |
string | No | can be used to maintain state after redirecting the user agent |
Finnaly the web browser will be redirected to the redirect URL (needs to be registered along the client ID at ezeep):
<your redirect_uri>/?code=<authorization_code>
Example:
https://account.ezeep.com/oauth/authorize?response_type=code&client_id=78KYzeX5wS8r0FYz9KdvNt9xHMRA61PJK80IHwNj&redirect_uri=https://www.ezeep.com&scope=printing&state=8b1a59d6af933215414d332b1dabb095&code_challenge=b844nDSnoL45VVqLCq8D1tCbmy7beAS3bYiREsCPnzs&code_challenge_method=S256
Direct Response:
HTTP/1.1 301 Moved Permanently
Date: Wed, 18 Jan 2023 13:46:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: /oauth/authorize/?response_type=code&client_id=78KYzeX5wS8r0FYz9KdvNt9xHMRA61PJK80IHwNj&redirect_uri=https://www.ezeep.com
If you are not already signed in to your ezeep account you will be asked to sign in.
After successful authentication you will be redirected to the relevant url with the authorization code as a url parameter
- `<your redirect_uri>/?code=<authorization_code>`
Example URL finnaly redirected to:
shell
https://www.foo.com/?code=UWvSbESYQ2SirrvPCoasSRVYK6jDDD
Request Access Token
Now that you have an Authorization Code, you must exchange it for tokens. Using the extracted Authorization Code (code) from the previous step. For all following requests mentioned in this article you will need an Authorization HTTP(S) header value.
POST https://account.ezeep.com/oauth/access_token/
Supported attributes:
Attribute | Type | Required | Description |
---|---|---|---|
Authorization |
HTTP Header | yes | Basic {{base_64_encoded_client_id}} |
Content-Type |
HTTP Header | yes | application/x-www-form-urlencoded |
grant_type |
string | yes | authorization_code |
scope |
string | yes |
printing reporting (space sperated scope list) |
code |
string | yes | code passed to call to redirect URL |
redirect_uri |
string | yes | <your redirect_uri> |
code_verifier |
string | yes | The initially created code_verifier string |
If successful, returns HTTP status code and the following response attributes:
Attribute | Type | Description |
---|---|---|
access_token |
string | the access token that is required in Authorization header of API requests |
token_type |
string | for ezeep Blue always “Bearer”, has to be passed in Authorization header |
expires_in |
int | validity time in seconds of the access token |
scope |
string | scope(s) of token |
refresh_token |
string | [optionally] refresh token, to be used for getting an new access token |
Example Request
curl -X POST "https://account.ezeep.com/oauth/access_token/" \
--header "Authorization: Basic NzhLWXplWDV3UzhyMEZZejlLZHZOdDl4SE1SQTYxUEpLODBJSHdOajo=" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data "grant_type=authorization_code" \
--data "scope=printing reporting" \
--data "code=<authorization_code>" \
--data "redirect_uri=<your redirect_uri>" \
--data "code_verifier=<your code_verifier>"
Example Response
{
"access_token": "eyJ0eXAiO...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "printing reporting",
"refresh_token": "erliDdAb..."
}
The access_token
will be valid for 3600 seconds (i.e. 1 hour) and after that duration you have to request new access token using the refresh token that you received in the access token response.
Use Refresh Token
You can use the refresh_token to get a new access_token. Usually, a user will need a new access_token only after the previous one expires or when gaining access to a new resource (with extended/different scope) for the first time. It’s bad practice to call the endpoint to get a new access_token every time you call an API, rate limiting for this endpoint may be applied.
According to RFC7009, a client should revoke the refresh token when no longer needed. This allows the authorization server to clean up security credentials. A revocation request will invalidate the actual refresh token and, if applicable, other refresh tokens based on the same authorization grant. Check the Token Revocation Article or RFC7009 for a detailed description.
To refresh your token, make a POST request to the /oauth/token
endpoint in the Authentication API, using grant_type=refresh_token
curl -X POST https://account.ezeep.com/oauth/access_token/
Supported attributes:
Attribute | Parameter Type | Required | Description |
---|---|---|---|
Authorization |
HTTP Header | yes | Basic {{base_64_encoded_client_id}} |
Content-Type |
HTTP Header | yes | application/x-www-form-urlencoded |
grant_type |
string | yes | refresh_token |
scope |
string | yes |
printing reporting (space sperated scope list) |
refresh_token |
string | yes | refresh_token obtained by last (token rotation) call to /oauth/access_token |
If successful, returns HTTP status code and the following response attributes:
Attribute | Type | Description |
---|---|---|
access_token |
string | the access token that is required in Authorization header of API requests |
token_type |
string | for ezeep Blue always “Bearer”, has to be passed in Authorization header |
expires_in |
int | validity time in seconds of the access token |
scope |
string | scope(s) of token |
refresh_token |
string | refresh token, to be used for getting an new access token |
Example Request
curl -X POST "https://account.ezeep.com/oauth/access_token/" \
--header "Authorization: Basic NzhLWXplWDV3UzhyMEZZejlLZHZOdDl4SE1SQTYxUEpLODBJSHdOajo=" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data "grant_type=refresh_token" \
--data "scope=printing reporting" \
--data "refresh_token=qX5HTLt4..."
Example Response
{
"access_token": "eyJ0eXAiOiJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "printing reporting",
"refresh_token": "vT5GTKk8..."
}
You will need to replace and store the new refresh token securely from the response for future usage.
Example bash script
#!/bin/bash
# Configuration - Update these values for your setup
BASE_URL="https://account.ezeep.com"
CLIENT_ID="78KYzeX5wS8r0FYz9KdvNt9xHMRA61PJK80IHwNj"
CLIENT_SECRET="" # Only needed for confidential clients
REDIRECT_URI="https://www.ezeep.com/"
SCOPE="printing"
# Generate PKCE parameters
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d "=+/" | tr -d '\n')
CODE_CHALLENGE_METHOD="S256"
# Generate random state parameter
STATE=$(openssl rand -hex 16)
echo "=== OAuth 2.0 Authorization Code Flow with PKCE ==="
echo "Code Verifier: $CODE_VERIFIER"
echo "Code Challenge: $CODE_CHALLENGE"
echo "State: $STATE"
echo ""
# Step 1: Authorization Request (this will redirect to login page)
AUTH_URL="${BASE_URL}/oauth/authorize/?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&state=${STATE}&code_challenge=${CODE_CHALLENGE}&code_challenge_method=${CODE_CHALLENGE_METHOD}"
echo "Step 1: Open this URL in your browser to authorize:"
echo "$AUTH_URL"
echo ""
echo "After authorization, you'll be redirected to:"
echo "${REDIRECT_URI}?code=AUTHORIZATION_CODE&state=${STATE}"
echo ""
# Wait for user to provide the authorization code
read -p "Enter the authorization code from the callback URL: " AUTHORIZATION_CODE
echo ""
echo "Step 2: Exchange authorization code for access token"
# Step 2: Token Request
TOKEN_RESPONSE=$(curl -s -X POST "${BASE_URL}/oauth/access_token/" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic $(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64)" \
-d "grant_type=authorization_code" \
-d "code=${AUTHORIZATION_CODE}" \
-d "redirect_uri=${REDIRECT_URI}" \
-d "client_id=${CLIENT_ID}" \
-d "code_verifier=${CODE_VERIFIER}")
echo "Token Response:"
echo "$TOKEN_RESPONSE" | jq . 2>/dev/null || echo "$TOKEN_RESPONSE"
echo ""
# Extract access token for further use
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token // empty' 2>/dev/null)
if [ ! -z "$ACCESS_TOKEN" ]; then
echo "Step 3: Test API call with access token"
# Test with user info endpoint (if available)
USER_INFO=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" "${BASE_URL}/oidc/user_info/")
echo "User Info Response:"
echo "$USER_INFO" | jq . 2>/dev/null || echo "$USER_INFO"
else
echo "Failed to obtain access token. Check the response above for errors."
fi