# Refresh Token

Exchange a refresh token for a new access token without asking the user to sign in again.

## Overview

Send the `refreshToken` you received from [Login](https://docs.aureahub.com/docs/auth-login.md), [Register](https://docs.aureahub.com/docs/auth-register.md), or Google/Apple sign-in. The response contains a new `accessToken` and its `expiresIn` (`900` seconds).

- **No signature, no bearer token.** This route checks neither the tenant HMAC headers nor an `Authorization` header. The refresh token in the body is the credential.
- **Current claims.** The new access token's `role` and `tenantId` are read from the user's current record, so a role or tenant change takes effect at the next refresh.
- **Lifetime.** Refresh tokens expire after 7 days by default. The lifetime is a server setting.
- **Password changes.** A refresh token issued before the user's password was last changed is rejected. See [Forgot Password](https://docs.aureahub.com/docs/auth-forgot-password.md).
- **Rate limit.** 60 requests per minute per client IP.

## Endpoint

### `POST /v1/auth/refresh`

Returns a new access token for a valid refresh token. No tenant signature and no bearer token are needed.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `refreshToken` | string | yes | A refresh token previously issued by the API |

**Responses**

`200` OK

```json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 900
}
```

`200` OK (rotation on)

```json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 900,
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}
```

`401` Unauthorized

```json
{ "error": "UnauthorizedError", "message": "Invalid or expired refresh token" }
```

`400` Bad Request

```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Request validation failed",
  "details": [
    {
      "instancePath": "",
      "schemaPath": "#/required",
      "keyword": "required",
      "params": { "missingProperty": "refreshToken" },
      "message": "must have required property 'refreshToken'"
    }
  ]
}
```

`429` Too Many Requests

```json
{ "statusCode": 429, "error": "Error", "message": "Rate limit exceeded, retry in 1 minute" }
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refreshToken":"<refresh-token>"}'
```

## Rotation

Refresh-token rotation is a server setting and is **off by default**. Whether it is enabled in production is deployment-specific, so handle both cases:

- **Rotation off:** the response has no `refreshToken`. Keep using the same refresh token until it expires.
- **Rotation on:** the response includes a new `refreshToken`, and the token you sent is used up. Store the new one every time. Presenting a refresh token that was already used is treated as a replay: every refresh token issued from the same sign-in is revoked, and the call returns `401` *Refresh token reuse detected — please sign in again*.

> ⚠️ With rotation on, never send the same refresh token twice. That includes two parallel requests or two browser tabs. Run one refresh at a time and share its result.

## Errors

On this endpoint, `401` bodies contain only `error` and `message`; `400` and `429` use the full envelope described in [Error Handling](https://docs.aureahub.com/docs/errors.md). Treat any `401` as "sign the user in again".

| Status | message | When |
| --- | --- | --- |
| 401 | `Invalid or expired refresh token` | The token is malformed, badly signed or expired, or its user can no longer be loaded. |
| 401 | `Invalid token type` | The token is not a refresh token (for example, an access token). |
| 401 | `Account is not active` | The user's account is not active. |
| 401 | `Session expired after a password change — please sign in again` | The token was issued before the user's password was last changed. |
| 401 | `Refresh token is no longer valid` | Rotation on: the token has been revoked or has expired. |
| 401 | `Refresh token reuse detected — please sign in again` | Rotation on: the token was already used. All refresh tokens from that sign-in are now revoked. |
| 400 | `Request validation failed` | The body has no refreshToken. details lists the problem. |
| 429 | `Rate limit exceeded, retry in 1 minute` | More than 60 requests in one minute from the same client IP. See the retry-after header. |

## Implementation

A refresh helper that runs at most one refresh at a time and stores a rotated refresh token when one is returned, plus a wrapper that refreshes once and retries once when a protected call returns `401`:

```javascript
const API = 'https://api.aureahub.com';
let refreshInFlight = null;

// store: { accessToken, refreshToken }
export function refreshSession(store) {
  // One refresh at a time: with rotation on, a second call with the same token counts as reuse.
  refreshInFlight ??= (async () => {
    try {
      const res = await fetch(API + '/v1/auth/refresh', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refreshToken: store.refreshToken }),
      });
      if (res.status === 401) throw new Error('RE_AUTH_REQUIRED'); // sign the user in again
      if (!res.ok) throw new Error(`Refresh failed with HTTP ${res.status}`);

      const { accessToken, expiresIn, refreshToken } = await res.json();
      store.accessToken = accessToken;
      if (refreshToken) store.refreshToken = refreshToken; // only returned when rotation is on
      return { accessToken, expiresIn };
    } finally {
      refreshInFlight = null;
    }
  })();
  return refreshInFlight;
}

// Call a bearer-protected route; on 401, refresh once and retry once.
export async function apiFetch(store, path, init = {}) {
  const call = () => fetch(API + path, {
    ...init,
    headers: { ...init.headers, Authorization: `Bearer ${store.accessToken}` },
  });

  let res = await call();
  if (res.status !== 401) return res;

  await refreshSession(store);
  res = await call();
  if (res.status === 401) throw new Error('RE_AUTH_REQUIRED');
  return res;
}
```

---

Web version: https://docs.aureahub.com/#auth-refresh
