> For the complete documentation index, see [llms.txt](https://docs.tiun.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tiun.io/guides/authentication/verify-server-side.md).

# Verify authentication server-side

Client-side checks (`isAuthenticated`, `userChange`) are enough for UI gating, but your backend should not trust the browser alone. This guide walks you through verifying a tiun user's identity on your server before serving protected data.

<figure><img src="/files/VBoSsKq6aXmgDzIpGLAa" alt="Verify user flow: frontend gets token, sends to backend, backend exchanges with tiun API for the user object"><figcaption></figcaption></figure>

***

## How it works

1. Your frontend asks tiun for a signed verification token.
2. It sends that token to your backend on protected requests.
3. Your backend exchanges the token with the [tiun UserVerification API](https://app.gitbook.com/s/ZMTdS5A9nvRqJOIo1RJM/userverification) for the user object.
4. If the user object reports `isAuthenticated: true`, you serve the protected data.

The token is a **signed JWT**, valid for **5 minutes**. The verified user object includes `isAuthenticated` plus a `userInfo` payload (`userId`, `email`, `productAccess`) — see [User object](/reference/authentication/user-object.md) in Reference for the full shape.

***

## Setup: API key

Before you can verify users on your server, create an API key in the dashboard: open **APIs** in the sidebar and click **Create new key**. Store it securely in your backend environment variables.

***

## 1. Get the verification token

Call `getUserVerificationToken()` on the frontend. It returns a token if the user is authenticated, or `null` if they're not. Attach it to your API request — most commonly as a Bearer token in the `Authorization` header.

```javascript
async function fetchProtectedData() {
  const token = await tiun.getUserVerificationToken();

  if (!token) {
    return null;
  }

  const response = await fetch('/api/protected', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  if (!response.ok) return null;
  return response.json();
}
```

***

## 2. Verify the token on your server

Call the [tiun UserVerification API](https://app.gitbook.com/s/ZMTdS5A9nvRqJOIo1RJM/userverification) to exchange the token for the user object.

**Endpoint:**

`POST /live_api/s2s/v1/users/verification`

**Base URLs:**

| Environment | URL                             |
| ----------- | ------------------------------- |
| Live        | `https://api.tiun.live`         |
| Sandbox     | `https://api-sandbox.tiun.live` |

Use the base URL and API key from the **same environment** as your frontend (`sandbox: true` in the SDK → sandbox URL and sandbox key). API keys are not shared between live and sandbox.

**Header:** `X-TIUN-API-KEY: <your-api-key>`

**Body:** `{ "userVerificationToken": "<token>" }`

**Response codes:**

| Status | Meaning                              |
| ------ | ------------------------------------ |
| `200`  | User object returned — read the body |
| `401`  | API key is invalid                   |

A `200` response carries the user object:

```json
{
  "isAuthenticated": true,
  "userInfo": {
    "userId": "u-...",
    "email": "user@example.com",
    "productAccess": ["p-live-..."]
  }
}
```

***

## 3. Check `isAuthenticated`

Once you have the user object, gate the response on `isAuthenticated`. If it's `false`, treat the request as unauthenticated — the API key was accepted but the underlying user session is no longer active.

For subscription-specific gating on top of identity, see [server-side subscription verification](/guides/subscriptions/verify-server-side.md) — same endpoint, plus a `productAccess` check.

***

## Full round-trip

**Frontend** — request protected data after auth state is known:

```javascript
tiun.on('userChange', async (data) => {
  if (!data.isAuthenticated) {
    showPublicContent();
    return;
  }

  const result = await fetchProtectedData();

  if (result) {
    showProtectedContent(result);
  }
});

async function fetchProtectedData() {
  const token = await tiun.getUserVerificationToken();

  if (!token) return null;

  const res = await fetch('/api/protected', {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (!res.ok) return null;
  return res.json();
}
```

**Backend** — exchange the token for the user object and serve:

```javascript
import express from 'express';

const app = express();

const BASE_URL = process.env.TIUN_API_BASE || 'https://api-sandbox.tiun.live';
const API_KEY = process.env.TIUN_API_KEY;

app.get('/api/protected', async (req, res) => {
  const auth = req.headers.authorization;
  const token = auth?.startsWith('Bearer ') ? auth.slice(7) : null;

  if (!token) {
    // missing token → reject as unauthenticated
    return;
  }

  const tiunResponse = await fetch(
    `${BASE_URL}/live_api/s2s/v1/users/verification`,
    {
      method: 'POST',
      headers: {
        'X-TIUN-API-KEY': API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ userVerificationToken: token }),
    },
  );

  if (tiunResponse.status !== 200) {
    // API key invalid or upstream error → fail closed
    return;
  }

  const user = await tiunResponse.json();

  if (!user.isAuthenticated) {
    // token was exchanged but the user has no active session → reject
    return;
  }

  // user authenticated → serve the protected content
  // user.userInfo carries userId, email, productAccess
});
```

***

{% hint style="info" %}
Live and sandbox are independent environments — each has its own API base URL and API keys. Use sandbox credentials while your app runs with `sandbox: true`; switch URL and key together when you ship live traffic. See [Sandbox](/reference/generic/sandbox.md).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tiun.io/guides/authentication/verify-server-side.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
