> ## Documentation Index
> Fetch the complete documentation index at: https://aftercaredocs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Authenticating with the AfterCare API Using JWT Tokens

> Register, log in, and authenticate AfterCare API requests with short-lived JWT access tokens. Covers token refresh rotation and logout.

The AfterCare API uses JSON Web Tokens (JWT) for authentication. When you register or log in, the API issues two tokens: a short-lived **access token** you attach to every protected request, and a longer-lived **refresh token** you use to obtain a new access token when the current one expires. No API keys or cookies are involved — all authentication state lives in these two tokens.

<Note>
  Pass your access token in the `Authorization` header on every protected request:

  ```http theme={null}
  Authorization: Bearer <accessToken>
  ```

  The `/auth/*` routes, `GET /health`, and `GET /drive/callback` are public and do not require this header.
</Note>

## Auth Flow

<Steps>
  ### Create an account

  Call `POST /auth/register` with your email and a password of at least 12 characters. On success, the API returns `201 Created` with a fresh access token and refresh token. You are immediately authenticated — no separate login step is required after registration.

  <ParamField body="email" type="string" required>
    A valid email address. Stored and matched in lowercase.
  </ParamField>

  <ParamField body="password" type="string" required>
    A plaintext password between 12 and 128 characters. Stored securely — never in plaintext.
  </ParamField>

  <CodeGroup>
    ```bash cURL theme={null}
    curl -s -X POST https://aftercare-web-eta.vercel.app/api/auth/register \
      -H "Content-Type: application/json" \
      -d '{"email": "alex@example.com", "password": "my-secure-passphrase"}'
    ```

    ```js fetch theme={null}
    const res = await fetch(
      "https://aftercare-web-eta.vercel.app/api/auth/register",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "alex@example.com",
          password: "my-secure-passphrase",
        }),
      }
    );
    const data = await res.json();
    // data.accessToken, data.refreshToken
    ```
  </CodeGroup>

  **Response — `201 Created`**

  <ResponseField name="user" type="object">
    Basic information about the newly created account.

    <Expandable title="user fields">
      <ResponseField name="user.id" type="string">
        The unique identifier for the account (UUID).
      </ResponseField>

      <ResponseField name="user.email" type="string">
        The normalised (lowercase) email address.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="accessToken" type="string" required>
    A signed JWT valid for **15 minutes**. Include this in the `Authorization: Bearer` header on every protected request.
  </ResponseField>

  <ResponseField name="refreshToken" type="string" required>
    A signed JWT valid for **7 days**. Store this securely and use it to obtain a new access token via `POST /auth/refresh`.
  </ResponseField>

  <ResponseField name="accessExpiresInSeconds" type="number">
    Lifetime of the access token in seconds (`900`).
  </ResponseField>

  <ResponseField name="refreshExpiresInSeconds" type="number">
    Lifetime of the refresh token in seconds (`604800`).
  </ResponseField>

  ```json theme={null}
  {
    "user": { "id": "a1b2c3d4-...", "email": "alex@example.com" },
    "accessToken": "<accessToken>",
    "refreshToken": "<refreshToken>",
    "accessExpiresInSeconds": 900,
    "refreshExpiresInSeconds": 604800
  }
  ```

  ### Log in to an existing account

  Call `POST /auth/login` with your credentials. The API verifies your password and returns a new token pair. The response shape is identical to registration but uses `200 OK`.

  <ParamField body="email" type="string" required>
    The email address you registered with.
  </ParamField>

  <ParamField body="password" type="string" required>
    Your account password.
  </ParamField>

  <CodeGroup>
    ```bash cURL theme={null}
    curl -s -X POST https://aftercare-web-eta.vercel.app/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{"email": "alex@example.com", "password": "my-secure-passphrase"}'
    ```

    ```js fetch theme={null}
    const res = await fetch(
      "https://aftercare-web-eta.vercel.app/api/auth/login",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "alex@example.com",
          password: "my-secure-passphrase",
        }),
      }
    );
    const { accessToken, refreshToken } = await res.json();
    ```
  </CodeGroup>

  **Response — `200 OK`**

  The response body is identical to the registration response above: `user`, `accessToken`, `refreshToken`, `accessExpiresInSeconds`, and `refreshExpiresInSeconds`.

  <Info>
    The API returns a generic `401` error for both wrong email and wrong password to prevent email enumeration. A `409` is returned only on registration when the email is already taken.
  </Info>

  ### Use the access token

  Attach the access token to every request that requires authentication using the `Authorization` header:

  ```http theme={null}
  Authorization: Bearer <accessToken>
  ```

  Access tokens expire after **15 minutes**. When a protected endpoint returns `401 Unauthorized`, move on to the next step and rotate your tokens.

  <CodeGroup>
    ```bash cURL theme={null}
    curl -s https://aftercare-web-eta.vercel.app/api/medications?documentId=<uuid> \
      -H "Authorization: Bearer <accessToken>"
    ```

    ```js fetch theme={null}
    const res = await fetch(
      "https://aftercare-web-eta.vercel.app/api/medications?documentId=<uuid>",
      {
        headers: { Authorization: `Bearer ${accessToken}` },
      }
    );
    ```
  </CodeGroup>

  ### Refresh your access token

  Call `POST /auth/refresh` with your current refresh token before — or immediately after — the access token expires. The API issues a brand-new token pair and invalidates your old refresh token.

  <Warning>
    The refresh token you send to `/auth/refresh` is **immediately revoked**. Save the new `refreshToken` from the response before discarding the old one. If you reuse a revoked refresh token, the API revokes **all** active sessions for your account as a security measure.
  </Warning>

  <ParamField body="refreshToken" type="string" required>
    Your current valid refresh token. After this call succeeds, this token is permanently revoked.
  </ParamField>

  <CodeGroup>
    ```bash cURL theme={null}
    curl -s -X POST https://aftercare-web-eta.vercel.app/api/auth/refresh \
      -H "Content-Type: application/json" \
      -d '{"refreshToken": "<yourRefreshToken>"}'
    ```

    ```js fetch theme={null}
    const res = await fetch(
      "https://aftercare-web-eta.vercel.app/api/auth/refresh",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refreshToken: currentRefreshToken }),
      }
    );
    const { accessToken, refreshToken: newRefreshToken } = await res.json();
    // Replace the stored refresh token with newRefreshToken immediately.
    ```
  </CodeGroup>

  **Response — `200 OK`**

  The response body is identical to the login response: a fresh `accessToken` (15-minute lifetime) and `refreshToken` (7-day lifetime).

  | Error | Meaning |
  | - | - |
  | `401` — Invalid or expired refresh token | The JWT signature is invalid or the token has passed its 7-day expiry. |
  | `401` — Session expired or revoked | The token was already rotated or the session was logged out. |
</Steps>

## Logging Out

Call `POST /auth/logout` to revoke the current session. Pass your refresh token in the request body so the API can identify the session to revoke. This endpoint is idempotent — it always returns `204 No Content` regardless of whether the token was already revoked or the session never existed.

<ParamField body="refreshToken" type="string">
  The refresh token for the session you want to end. If omitted, the endpoint still returns `204` without taking any action.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -X POST https://aftercare-web-eta.vercel.app/api/auth/logout \
    -H "Content-Type: application/json" \
    -d '{"refreshToken": "<yourRefreshToken>"}'
  ```

  ```js fetch theme={null}
  await fetch("https://aftercare-web-eta.vercel.app/api/auth/logout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refreshToken: currentRefreshToken }),
  });
  // Response is 204 No Content — no body to parse.
  ```
</CodeGroup>

After logout, discard both the access token and the refresh token from your client. Any subsequent request using the old access token continues to be accepted until its 15-minute window closes, so log out promptly when a session ends.

<Tip>
  Build a lightweight token manager in your client that tracks the access token expiry (`accessExpiresInSeconds`) and calls `/auth/refresh` automatically before it lapses. This keeps your users seamlessly authenticated without forcing repeated logins.
</Tip>

## Token Lifetimes at a Glance

| Token | Lifetime | Usage |
| - | - | - |
| Access token | 15 minutes | `Authorization: Bearer` header on protected requests |
| Refresh token | 7 days | Body of `POST /auth/refresh` to rotate to a new pair |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.