// SOFTWARE
Auth API
The Vapor backend behind Auth — JWT issuance, refresh, and account lifecycle for email, Apple, Google, and guest sign-in.
Getting Started
Getting Started
Auth API is the Vapor server-side of the Auth Swift Package. A host app adds Auth as a package dependency, registers each RouteCollection (AuthController, RefreshTokenController, LogoutController, GuestAuthController, UpgradeController, AppleAuthController, GoogleAuthController, ForgotPasswordController, ResetPasswordController, ChangePasswordController, AccountDeletionController) against its Vapor Application, and supplies its own Fluent database driver — Auth itself stays driver-agnostic.
Authentication
Most endpoints are public: registration, login, refresh, social sign-in, guest sign-in, forgot-password, and reset-password. Endpoints that act on an existing session — logout, guest upgrade, change password, account deletion, and the profile lookup — require a Bearer JWT access token issued by one of the sign-in endpoints, verified by JWTMiddleware.
Reference server
The endpoints below are demonstrated end-to-end by the bundled demo/api Vapor app, which stores data in SQLite for local development and is started with swift run from demo/api.
ENDPOINTS
OVERVIEW
Registers a new user with an email and password, hashes the password with BCrypt, and returns a fresh access/refresh token pair.
REQUEST
{
"email": "jane@example.com",
"password": "correct-horse-battery-staple"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| string | The new user's email address. Must be unique. | |
| password | string | Plaintext password. Hashed with BCrypt before storage. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "3f9c0b2e-8a1d-4e77-9c2a-0a6b7e5d1f44",
"expiresAt": "2026-08-22T20:10:13Z",
"user": {
"id": "b1e5c8b0-3c9a-4c2a-9b6d-7e0f2c9a1e5c",
"email": "jane@example.com",
"displayName": null,
"isGuest": false
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | Signed JWT access token. |
| refreshToken | string | Opaque refresh token string. |
| expiresAt | string | ISO 8601 expiry date of the access token. |
| user | object | The newly created user's profile. |
| id | string | User UUID. |
| string | null | The user's email, null for guests. | |
| displayName | string | null | Always null — not yet settable via this endpoint. |
| isGuest | boolean | Always false for this endpoint. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| conflict | A user with this email already exists. |
OVERVIEW
Authenticates an existing user by email and password and returns a fresh access/refresh token pair.
REQUEST
{
"email": "jane@example.com",
"password": "correct-horse-battery-staple"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| string | The account's email address. | |
| password | string | Plaintext password, verified against the stored BCrypt hash. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "3f9c0b2e-8a1d-4e77-9c2a-0a6b7e5d1f44",
"expiresAt": "2026-08-22T20:10:13Z",
"user": {
"id": "b1e5c8b0-3c9a-4c2a-9b6d-7e0f2c9a1e5c",
"email": "jane@example.com",
"displayName": null,
"isGuest": false
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | Signed JWT access token. |
| refreshToken | string | Opaque refresh token string. |
| expiresAt | string | ISO 8601 expiry date of the access token. |
| user | object | The authenticated user's profile. |
| id | string | User UUID. |
| string | null | The user's email, null for guests. | |
| displayName | string | null | The user's display name, if set. |
| isGuest | boolean | Whether the account is an anonymous guest. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | The email is not found or the password is wrong. |
OVERVIEW
Exchanges a valid, non-expired refresh token for a new access token and a rotated refresh token. Refresh tokens are one-time use — the old one is deleted as soon as a new pair is issued.
REQUEST
{
"refreshToken": "3f9c0b2e-8a1d-4e77-9c2a-0a6b7e5d1f44"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| refreshToken | string | The refresh token previously issued by any sign-in endpoint. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "9a1d3f9c-4e77-0b2e-8c2a-1f445d6b7e0a",
"expiresAt": "2026-08-22T21:10:13Z",
"user": {
"id": "b1e5c8b0-3c9a-4c2a-9b6d-7e0f2c9a1e5c",
"email": "jane@example.com",
"displayName": null,
"isGuest": false
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | A fresh signed JWT access token. |
| refreshToken | string | A newly rotated refresh token — replaces the one just used. |
| expiresAt | string | ISO 8601 expiry date of the new access token. |
| user | object | The session's user profile. |
| id | string | User UUID. |
| string | null | The user's email, null for guests. | |
| displayName | string | null | Always null on this endpoint. |
| isGuest | boolean | Whether the account is an anonymous guest. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | The refresh token was not found or has expired. |
OVERVIEW
Invalidates the supplied refresh token, ending the session. Requires a Bearer JWT. Idempotent — a token that is already deleted or never existed still returns HTTP 200 rather than an error.
REQUEST
{
"refreshToken": "3f9c0b2e-8a1d-4e77-9c2a-0a6b7e5d1f44"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| refreshToken | string | The refresh token to invalidate. |
RESPONSE
{}ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | Missing or invalid Bearer JWT. |
OVERVIEW
Creates a new anonymous user tied to a device identifier and returns a fresh access/refresh token pair. Each call creates a distinct guest account — the client is responsible for persisting and reusing the returned tokens rather than calling this repeatedly.
REQUEST
{
"deviceID": "F1A2B3C4-D5E6-4A7B-8C9D-0E1F2A3B4C5D"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| deviceID | string | A stable device identifier generated and persisted by the host app. Recorded but not used for de-duplication. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "5d1f44e0-9c2a-8a1d-4e77-3f9c0b2e0a6b",
"expiresAt": "2026-08-22T20:10:13Z",
"user": {
"id": "d4c1a9e2-7b3f-4a0d-9e5c-2b8f1a6d3c7e",
"email": null,
"displayName": null,
"isGuest": true
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | Signed JWT access token for the new guest session. |
| refreshToken | string | Opaque refresh token string. |
| expiresAt | string | ISO 8601 expiry date of the access token. |
| user | object | The new guest user's profile. |
| id | string | The guest's UUID — the stable identifier for this session. |
| string | null | Always null for guests. | |
| displayName | string | null | Always null. |
| isGuest | boolean | Always true. |
OVERVIEW
Attaches email/password or social credentials to the authenticated guest session, preserving the guest UUID so no app data is lost. Requires a Bearer JWT for the guest session being upgraded. Supply exactly one credential group: email + password for an email upgrade, or provider + identityToken for a social upgrade.
REQUEST
{
"guestUUID": "d4c1a9e2-7b3f-4a0d-9e5c-2b8f1a6d3c7e",
"provider": "email",
"email": "jane@example.com",
"password": "correct-horse-battery-staple",
"identityToken": null
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| guestUUID | string | The UUID of the guest session to upgrade. |
| provider | string | "email", "apple", or "google". |
| string | null | Required for an email upgrade; optional for social upgrades when the provider token doesn't carry one. | |
| password | string | null | Required for an email upgrade; hashed server-side. |
| identityToken | string | null | Required for apple/google upgrades — the identity token from the provider's SDK. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "0a6b7e5d-1f44-9c2a-8a1d-4e773f9c0b2e",
"expiresAt": "2026-08-22T20:10:13Z",
"user": {
"id": "d4c1a9e2-7b3f-4a0d-9e5c-2b8f1a6d3c7e",
"email": "jane@example.com",
"displayName": null,
"isGuest": false
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | Signed JWT access token for the upgraded session. |
| refreshToken | string | Opaque refresh token string. |
| expiresAt | string | ISO 8601 expiry date of the access token. |
| user | object | The upgraded user's profile — same UUID as the guest session. |
| id | string | Same UUID the guest session already had. |
| string | null | The newly attached email address. | |
| displayName | string | null | Always null on this endpoint. |
| isGuest | boolean | Always false after a successful upgrade. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | Missing/invalid Bearer JWT, or the user was not found. |
| conflict | The account has already been upgraded, or the new email is already taken. |
| badRequest | Required fields for the chosen provider are missing. |
OVERVIEW
Verifies an Apple identity token (from ASAuthorizationController) and signs the user in, creating an account on first sign-in. Apple only includes the user's email on the very first sign-in for a given app — on repeat sign-ins a stable synthetic address is derived from the token's subject claim so the same Apple ID always maps to the same account.
REQUEST
{
"provider": "apple",
"identityToken": "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ..."
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| provider | string | Always "apple". |
| identityToken | string | The identity token returned by ASAuthorizationController. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "7e0f2c9a-1e5c-3c9a-4c2a-9b6db1e5c8b0",
"expiresAt": "2026-08-22T20:10:13Z",
"user": {
"id": "9b6d7e0f-2c9a-1e5c-3c9a-4c2ab1e5c8b0",
"email": "jane@privaterelay.appleid.com",
"displayName": null,
"isGuest": false
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | Signed JWT access token. |
| refreshToken | string | Opaque refresh token string. |
| expiresAt | string | ISO 8601 expiry date of the access token. |
| user | object | The signed-in user's profile. |
| id | string | User UUID. |
| string | null | Real email on first sign-in, otherwise a synthetic @privaterelay.appleid.com address. | |
| displayName | string | null | Always null. |
| isGuest | boolean | Always false. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| internalServerError | Apple JWKS is not configured on the server. |
| unauthorized | The identity token is invalid or tampered. |
OVERVIEW
Verifies a Google identity token (from the Google Sign-In SDK) and signs the user in, creating an account on first sign-in.
REQUEST
{
"provider": "google",
"identityToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9..."
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| provider | string | Always "google". |
| identityToken | string | The ID token returned by the Google Sign-In SDK. |
RESPONSE
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "2b8f1a6d-3c7e-7b3f-4a0d-9e5cd4c1a9e2",
"expiresAt": "2026-08-22T20:10:13Z",
"user": {
"id": "4a0d9e5c-2b8f-1a6d-3c7e-7b3fd4c1a9e2",
"email": "jane@gmail.com",
"displayName": null,
"isGuest": false
}
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| accessToken | string | Signed JWT access token. |
| refreshToken | string | Opaque refresh token string. |
| expiresAt | string | ISO 8601 expiry date of the access token. |
| user | object | The signed-in user's profile. |
| id | string | User UUID. |
| string | null | The Google account's email address. | |
| displayName | string | null | Always null. |
| isGuest | boolean | Always false. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| internalServerError | Google JWKS is not configured on the server. |
| unauthorized | The identity token is invalid or tampered. |
OVERVIEW
Initiates a password reset for the given email. Always returns HTTP 200, whether or not the email is registered, to prevent user enumeration. If found, a one-hour reset token is generated and delivered via the host app's injected email transport closure.
REQUEST
{
"email": "jane@example.com"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| string | The email address of the account to reset. |
RESPONSE
{}ERRORS
| CODE | DESCRIPTION |
|---|---|
| internalServerError | No email transport closure is configured on the server (fail-fast). |
OVERVIEW
Completes a password reset using the one-time token delivered by /auth/forgot-password. The token is invalidated after use.
REQUEST
{
"token": "6c1b9a2d-4e7f-4c3a-8b0e-1d2f3a4b5c6d",
"newPassword": "new-correct-horse-battery"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| token | string | The one-time reset token delivered to the user's email. |
| newPassword | string | The new plaintext password. Hashed with BCrypt before storage. |
RESPONSE
{}ERRORS
| CODE | DESCRIPTION |
|---|---|
| badRequest | The reset token was not found or has expired. |
OVERVIEW
Changes the authenticated user's password. Only available to email-authenticated accounts — Apple, Google, and guest accounts have no stored password hash. Requires a Bearer JWT.
REQUEST
{
"currentPassword": "correct-horse-battery-staple",
"newPassword": "new-correct-horse-battery"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| currentPassword | string | The user's current plaintext password, verified against the stored hash. |
| newPassword | string | The desired new plaintext password. Hashed with BCrypt before storage. |
RESPONSE
{
"message": "Password changed"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| message | string | Always "Password changed" on success. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | Missing/invalid Bearer JWT, or currentPassword does not match the stored hash. |
| notFound | No user with the decoded ID exists. |
| unprocessableEntity | The account has no stored password (Apple, Google, or guest account). |
OVERVIEW
Permanently deletes the authenticated user and every refresh token associated with them. Requires a Bearer JWT. Takes no request body.
REQUEST
{}RESPONSE
{}ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | Missing or invalid Bearer JWT, or the subject claim can't be parsed as a UUID. |
| notFound | No user with the derived ID exists. |
OVERVIEW
Returns the authenticated user's profile and current token debug information. Registered by the demo/api reference server (MeController), not by the AuthServer package itself — a host app can mirror this controller or write its own. Requires a Bearer JWT. Takes no request body.
REQUEST
{}RESPONSE
{
"id": "b1e5c8b0-3c9a-4c2a-9b6d-7e0f2c9a1e5c",
"email": "jane@example.com",
"authProvider": "email",
"createdAt": "2026-08-01T09:12:00Z",
"isGuest": false,
"accessTokenExpiry": "2026-08-22T20:10:13Z",
"refreshTokenId": "3f9c0b2e-8a1d-4e77-9c2a-0a6b7e5d1f44"
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
| id | string | The authenticated user's UUID. |
| string | The user's email address. Empty string for guest users. | |
| authProvider | string | One of "email", "apple", "google", "guest". |
| createdAt | string | ISO 8601 date the account was created. |
| isGuest | boolean | Whether this is a guest (anonymous) account. |
| accessTokenExpiry | string | ISO 8601 expiry date of the access token used for this request. |
| refreshTokenId | string | The ID of the active refresh token record for this user. |
ERRORS
| CODE | DESCRIPTION |
|---|---|
| unauthorized | Missing or invalid Authorization header. |
| internalServerError | The user record or an active refresh token could not be found. |
TECHNOLOGY
This page is only available on the operator's home network.