---
title: Token Mutual Authentication
description: Token Mutual Authentication
---

[Skip to content](https://portal.sandgrain.eu/knowledge-base/token-mutual-authentication#main-content)

English

Show submenu for translations

![SandGrain\_Logo\_RGB\_white-bijgesneden (1)](https://portal.sandgrain.eu/hs-fs/hubfs/SandGrain_Logo_RGB_white-bijgesneden%20(1).png?width=427&height=83&name=SandGrain_Logo_RGB_white-bijgesneden%20(1).png)

Open main navigation

Close main navigation

- English
  
  Show submenu for translations
- [Contact us](https://portal.sandgrain.eu/login)

[Contact us](https://portal.sandgrain.eu/login?hsLang=en)

 How can we help you?

- There are no suggestions because the search field is empty.

1. [Knowledge Base](https://portal.sandgrain.eu/knowledge-base?hsLang=en)
2. [API Documentation](https://portal.sandgrain.eu/knowledge-base/api-documentation?hsLang=en)
3. [Device API Integration](https://portal.sandgrain.eu/knowledge-base/api-documentation?hsLang=en#device-api-integration)

October 6, 2026

# Token Mutual Authentication

![token-mutual-authentication-flow](https://portal.sandgrain.eu/hubfs/token-mutual-authentication-flow.svg)

Mutual authentication lets a device and the server verify each other's identity before a trusted session or token is issued. Neither side has to trust the other blindly: the server issues a **challenge word (CW)**, the device proves it can produce the correct **response word (RW)**, and the server confirms the result. Because the exchange runs in both directions, it's called **mutual authentication**.

The flow uses three endpoints, called in sequence:

1. **`POST /api/device/requestMutualAuthCW`** — the device requests a challenge word (CW) for its token ID (TID). The server returns the CW and a `transactionId` that ties the rest of the flow together.
2. **`POST /api/device/replyMutualAuthRW`** — the device computes its response word (RW) and sends it back along with the CW, TID, and `transactionId`.
3. **`GET /api/device/checkMutualAuthStatus`** — the device polls this endpoint with the `transactionId` to find out whether authentication succeeded.

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 mutual authentication challenge word based on the device's token ID (TID). The response includes a `transactionId` that you must carry through the remaining steps.

#### **Request**

```
POST /api/device/requestMutualAuthCW Content-Type: application/json CF-Access-Client-Id: <your-client-id> CF-Access-Client-Secret: <your-client-secret> 
```

#### **Request body**

```
{   "requestSignedResponse": false,       "TID": "800000001000000010000000000000014464b592da61f3fd5506fcb1f37f4bef" } 
```

| Field | Type | Description |
| --- | --- | --- |
| `TID` | string | The device token ID. Must be exactly **64 characters** long. |
| `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 to the server, along with the `CW`, `TID`, and the `transactionId` from Step 1.

#### **Request**

```
POST /api/device/replyMutualAuthRW 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 mutual authentication exchange.

#### **Request**

```
GET /api/device/checkMutualAuthStatus?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",                    "AUTH_NOT_OK",                    "CLAIM_TOKEN"       ],       "claimTokenId": "123e4567-e89b-12d3-a456-426614174000",       "HRW2": "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`. |
| `HRW2` | string | A hash of the server's response word, used by the device for the final verification step. |

#### **Status values**

| Value | Meaning |
| --- | --- |
| `AUTH_OK` | Mutual authentication succeeded. |
| `AUTH_NOT_OK` | Mutual 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

- Cloudflare Access service tokens — generating your `CF-Access-Client-Id` and `CF-Access-Client-Secret`
- Device token IDs (TIDs) — format and provisioning
- Claiming a token after `CLAIM_TOKEN`

- [User Documentation](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#main-content)

    - [Quick Starter Guide](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#quick-starter-guide)
    - [User Manual](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#user-manual)
    - [Whitepaper](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#whitepaper)
    - [Token Leaflet](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#token-leaflet)
    - [Claim Token Procedure](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#claim-token-procedure)
    - [Daisy Chain](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#daisy-chain)
    - [Release Note](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#release-note)
    - [Naming Convention](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#naming-convention)
    - [SandGrain SDK](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#sandgrain-sdk)
    - [TrustBridge](https://portal.sandgrain.eu/knowledge-base/user-documentation?hsLang=en#trustbridge)
- [API Documentation](https://portal.sandgrain.eu/knowledge-base/api-documentation?hsLang=en#main-content)

    - [Device API Integration](https://portal.sandgrain.eu/knowledge-base/api-documentation?hsLang=en#device-api-integration)
    - [Daisy Chain and Attestation](https://portal.sandgrain.eu/knowledge-base/api-documentation?hsLang=en#daisy-chain-and-attestation)
- [SDK](https://portal.sandgrain.eu/knowledge-base/sdk?hsLang=en#main-content)

    - [Core Functions](https://portal.sandgrain.eu/knowledge-base/sdk?hsLang=en#core-functions)
    - [SandGrain SDK](https://portal.sandgrain.eu/knowledge-base/sdk?hsLang=en#sandgrain-sdk)

[![SandGrain\_Logo\_RGB\_white-bijgesneden (1)-1](https://portal.sandgrain.eu/hs-fs/hubfs/SandGrain_Logo_RGB_white-bijgesneden%20(1)-1.png?width=427&height=83&name=SandGrain_Logo_RGB_white-bijgesneden%20(1)-1.png "SandGrain_Logo_RGB_white-bijgesneden (1)-1")](https://portal.sandgrain.eu/?hsLang=en)

<https://www.facebook.com/> <https://www.twitter.com/> <https://www.instagram.com/> <https://podcasts.apple.com/> [mailto:email@email.com](mailto:email@email.com)

Copyright © 2026, sandgrain.eu