Token Authentication with Ephemeral Key (EK)
The difference from the standard flow is:
- The first request includes a
recipientPublicKey(a PEM-encoded RSA public key). The ephemeral key is protected for this recipient. - The status response returns an extra
EKfield alongside the result.
The flow uses three endpoints, called in sequence:
POST /api/device/EKRequestCW— request a challenge word (CW) for the device's TID, supplying therecipientPublicKey. Returns the CW and atransactionId.POST /api/device/EKReplyRW— submit the response word (RW) along with the CW, TID, andtransactionId.GET /api/device/EKCheckAuthStatus— poll with thetransactionIdto retrieve the authentication result and the ephemeral key.
Every request must be authenticated at the edge with Cloudflare Access headers (see below).
Authentication headers
All three endpoints require the following Cloudflare Access service-token headers on every request:
| Header | Required | Description |
|---|---|---|
CF-Access-Client-Id |
Yes | Your Cloudflare Access client ID. |
CF-Access-Client-Secret |
Yes | Your Cloudflare Access client secret. |
If these headers are missing or invalid, the request is rejected with 403 Forbidden.
Step 1 — Request a challenge word (CW)
Generate a challenge word based on the device's token ID (TID), supplying the recipient's public key. The response includes a transactionId that you must carry through the remaining steps.
Request
POST /api/device/EKRequestCW
Content-Type: application/json
CF-Access-Client-Id: <your-client-id>
CF-Access-Client-Secret: <your-client-secret>
Request body
{
"requestSignedResponse": false,
"recipientPublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----",
"TID": "800000001000000010000000000000014464b592da61f3fd5506fcb1f37f4bef"
}
| Field | Type | Description |
|---|---|---|
TID |
string | The device token ID. Must be exactly 64 characters long. |
recipientPublicKey |
string | A PEM-encoded RSA public key. The ephemeral key (EK) is protected for this recipient. |
requestSignedResponse |
boolean | Set to true to request a signed response. |
Response — 201 Created
{
"CW": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"transactionId": "123e4567-e89b-12d3-a456-426614174000"
}
| Field | Type | Description |
|---|---|---|
CW |
string | The challenge word generated by the server. |
transactionId |
string (UUID) | Identifies this authentication process. Use it in the next two steps. |
Errors
| Code | Meaning |
|---|---|
400 Bad Request |
The request body is invalid — for example, "TID must be exactly 64 characters long". |
401 Unauthorized |
Authentication failed. |
403 Forbidden |
Invalid request, commonly caused by an invalid Cloudflare Access token. |
Step 2 — Reply with the response word (RW)
Compute the response word (RW) from the challenge word and submit it back, along with the CW, TID, and the transactionId from Step 1.
Request
POST /api/device/EKReplyRW
Content-Type: application/json
CF-Access-Client-Id: <your-client-id>
CF-Access-Client-Secret: <your-client-secret>
Request body
{
"requestSignedResponse": false,
"TID": "800000001000000010000000000000014464b592da61f3fd5506fcb1f37f4bef",
"CW": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"RW": "e498a506be93008e40739a3077e4ad40",
"transactionId": "c905f6b4-3239-417e-abd9-66fe66f43a2a"
}
| Field | Type | Description |
|---|---|---|
TID |
string | The device token ID (same value used in Step 1). |
CW |
string | The challenge word received in Step 1. |
RW |
string | The response word computed by the device. |
transactionId |
string (UUID) | The transaction ID returned in Step 1. |
requestSignedResponse |
boolean | Set to true to request a signed response. |
Response — 201 Created
{
"transactionId": "123e4567-e89b-12d3-a456-426614174000"
}
| Field | Type | Description |
|---|---|---|
transactionId |
string (UUID) | Confirms the transaction. Use it to check the result in Step 3. |
Errors
| Code | Meaning |
|---|---|
401 Unauthorized |
Authentication failed. |
403 Forbidden |
Invalid request, commonly caused by an invalid Cloudflare Access token. |
Step 3 — Check the authentication result
Poll this endpoint with the transactionId to retrieve the outcome of the authentication and the ephemeral key.
Request
GET /api/device/EKCheckAuthStatus?transactionId=<transactionId>
CF-Access-Client-Id: <your-client-id>
CF-Access-Client-Secret: <your-client-secret>
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transactionId |
string (UUID) | Yes | The transaction ID identifying the authentication process. |
requestSignedResponse |
boolean | No | Request a signed response. |
Response — 200 OK
{
"status": "AUTH_OK",
"claimTokenId": "123e4567-e89b-12d3-a456-426614174000",
"EK": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
| Field | Type | Description |
|---|---|---|
status |
string | The result of the authentication process. See possible values below. |
claimTokenId |
string (UUID) | The claim token ID, returned when the status is CLAIM_TOKEN. |
EK |
string | The ephemeral key, protected for the recipientPublicKey supplied in Step 1. |
Status values
| Value | Meaning |
|---|---|
AUTH_OK |
Authentication succeeded. |
AUTH_NOT_OK |
Authentication failed. |
CLAIM_TOKEN |
A token is ready to be claimed; use the returned claimTokenId. |
Errors
| Code | Meaning |
|---|---|
401 Unauthorized |
Authentication failed. |
403 Forbidden |
Invalid request, commonly caused by an invalid Cloudflare Access token. |
Related articles
- Token Mutual Authentication — the standard flow without an ephemeral key (
requestMutualAuthCW,replyMutualAuthRW,checkMutualAuthStatus) - Host Authentication with Ephemeral Key (EK) — the host auth EK flow (
EKRequestHostAuth,EKCheckRequestHostAuthStatus) - Cloudflare Access service tokens — generating your
CF-Access-Client-IdandCF-Access-Client-Secret - Claiming a token after
CLAIM_TOKEN
| ← Token Authentication | Host Authentication → |