WAY Auth logo

WAY Auth Service

API Documentation

Overview

WAY Auth is a standalone JWT authentication service for email/password logins. It issues short-lived access tokens, maintains refresh sessions via HttpOnly cookies, and publishes a JWKS endpoint so your backends can verify tokens without storing private keys.

Email + password login with password hashing
RS256 JWT access tokens with JWKS publishing
Refresh token rotation with server-side revocation
Admin-managed CORS allowlist

Quickstart Flow

1

Create a user

Call /api/v1/signup with email + password and x-way-signup-secret.

2

Log in

Call /api/v1/login to set the refresh cookie and receive an access token.

3

Use the access token

Send Authorization: Bearer <token> to /api/v1/me or your own APIs.

4

Refresh when needed

Call /api/v1/refresh with credentials included to rotate sessions and get a new access token.

Client login example

const response = await fetch('https://way-my-auth-service.vercel.app/api/v1/login', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({ email, password }),
});
const payload = await response.json();
// payload.accessToken contains the JWT

Refresh example

const refreshed = await fetch('https://way-my-auth-service.vercel.app/api/v1/refresh', {
  method: 'POST',
  credentials: 'include',
});
const data = await refreshed.json();
// data.accessToken is a new JWT

Authentication Model

The service returns access tokens in JSON responses and stores refresh tokens in an HttpOnly cookie. Keep access tokens in memory and refresh them when they expire. Use the JWKS endpoint to verify tokens on your backend.

Signup secret

Include the header x-way-signup-secret on all /api/v1/signup requests. Signups are rejected when the secret is missing or invalid.

Bearer access tokens

Send Authorization: Bearer <token> to /api/v1/me and your downstream APIs.

Token Details

Access Token TTL

15 min

Signed with RS256 using JWT_ISSUER and JWT_AUDIENCE.

Refresh Token TTL

30 days

Rotated on every refresh to prevent replay.

JWT Claims

sub, sid

Standard claims include iss, aud, iat, exp, jti.

JWKS

/api/v1/jwks

Use the published key set to verify JWTs server-side.

Refresh Cookie

Cookie attributes

  • HttpOnly, Path=/
  • Mode defaults: proxy => SameSite=Lax, cross-site => SameSite=None
  • Secure in production and always when SameSite=None
  • Max-Age defaults to 30 days (REFRESH_TOKEN_TTL_SECONDS)
  • Name set by REFRESH_COOKIE_NAME

Client requirement

Browser clients must send credentials: 'include' so the refresh cookie is set and sent with requests.

CORS

CORS is enforced against a server-managed allowlist. Same-origin requests are always allowed. If you need a browser app from another origin, add it via the admin CORS endpoints.

Allowed headers

content-type, authorization, x-way-signup-secret

Allowed methods

GET, POST, PATCH, DELETE, OPTIONS

Rate Limits

Sliding-window limits are enforced per IP. When exceeded, the API returns 429 with error code rate_limited.

/api/v1/signup

5 / 10 min

/api/v1/login

10 / 10 min

/api/v1/refresh

20 / 10 min

Error Format

All error responses are JSON with a consistent error.code and error.message.

{
  "error": {
    "code": "invalid_input",
    "message": "Request payload is invalid."
  }
}

Endpoints

Each endpoint below includes required auth, payloads, and examples.

Auth Endpoints

Core email/password auth and JWT issuance.

POST/api/v1/signup

Create account

Create a user and issue access + refresh tokens.

Auth

Required signup secret header.

Rate Limit

5 requests / 10 minutes / IP

Headers

  • content-type: application/json
  • x-way-signup-secret: <secret>

Request body

{
  "email": "you@example.com",
  "password": "strong-password"
}

Response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com"
  },
  "accessToken": "<jwt>",
  "tokenType": "Bearer",
  "expiresIn": 900
}

Errors

invalid_signup_secretinvalid_inputinvalid_jsonemail_takenrate_limited

Example request

curl -X POST https://way-my-auth-service.vercel.app/api/v1/signup \
  -H 'content-type: application/json' \
  -H 'x-way-signup-secret: <secret>' \
  -d '{"email":"you@example.com","password":"strong-password"}'

Example response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com"
  },
  "accessToken": "<jwt>",
  "tokenType": "Bearer",
  "expiresIn": 900
}
POST/api/v1/login

Log in

Authenticate credentials and issue access + refresh tokens.

Auth

Email/password.

Rate Limit

10 requests / 10 minutes / IP

Headers

  • content-type: application/json

Request body

{
  "email": "you@example.com",
  "password": "strong-password"
}

Response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com"
  },
  "accessToken": "<jwt>",
  "tokenType": "Bearer",
  "expiresIn": 900
}

Errors

invalid_credentialsinvalid_inputinvalid_jsonrate_limited

Example request

curl -X POST https://way-my-auth-service.vercel.app/api/v1/login \
  -H 'content-type: application/json' \
  -d '{"email":"you@example.com","password":"strong-password"}'

Example response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com"
  },
  "accessToken": "<jwt>",
  "tokenType": "Bearer",
  "expiresIn": 900
}
POST/api/v1/refresh

Refresh access token

Rotate the refresh session and issue a new access token. No request body is required.

Auth

Refresh token cookie.

Rate Limit

20 requests / 10 minutes / IP

Headers

  • cookie: <refresh cookie>

Response

{
  "accessToken": "<jwt>",
  "tokenType": "Bearer",
  "expiresIn": 900
}

Errors

missing_refresh_tokenexpired_refresh_tokeninvalid_refresh_tokenrate_limited

Example request

curl -X POST https://way-my-auth-service.vercel.app/api/v1/refresh \
  --cookie 'way_refresh=<refresh-token>'

Example response

{
  "accessToken": "<jwt>",
  "tokenType": "Bearer",
  "expiresIn": 900
}
POST/api/v1/logout

Log out

Revoke the refresh session and clear the refresh cookie.

Auth

Refresh token cookie (if present).

Headers

  • cookie: <refresh cookie>

Response

{
  "success": true
}

Example request

curl -X POST https://way-my-auth-service.vercel.app/api/v1/logout

Example response

{
  "success": true
}
GET/api/v1/me

Get current user

Resolve the current user from the access token.

Auth

Bearer access token.

Headers

  • authorization: Bearer <access-token>

Response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com"
  },
  "sessionId": "session_123"
}

Errors

missing_bearer_tokeninvalid_token

Example request

curl https://way-my-auth-service.vercel.app/api/v1/me \
  -H 'authorization: Bearer <access-token>'

Example response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com"
  },
  "sessionId": "session_123"
}
GET/api/v1/jwks

JWKS

Fetch public keys used to verify access tokens.

Auth

None.

Errors

internal_error

Example request

curl https://way-my-auth-service.vercel.app/api/v1/jwks

Example response

{
  "keys": [
    {
      "kty": "RSA",
      "alg": "RS256",
      "use": "sig",
      "kid": "<kid>",
      "n": "<modulus>",
      "e": "AQAB"
    }
  ]
}
GET/.well-known/way-auth-configuration

Discovery configuration

Fetch issuer, audience, JWKS URL, and endpoint URLs for SDK auto-configuration.

Auth

None.

Example request

curl https://way-my-auth-service.vercel.app/.well-known/way-auth-configuration

Example response

{
  "version": "1",
  "issuer": "https://way-my-auth-service.vercel.app",
  "audience": "way-clients",
  "jwks_url": "https://way-my-auth-service.vercel.app/api/v1/jwks",
  "endpoints": {
    "signup": "https://way-my-auth-service.vercel.app/api/v1/signup",
    "login": "https://way-my-auth-service.vercel.app/api/v1/login",
    "refresh": "https://way-my-auth-service.vercel.app/api/v1/refresh",
    "logout": "https://way-my-auth-service.vercel.app/api/v1/logout",
    "me": "https://way-my-auth-service.vercel.app/api/v1/me"
  }
}

Admin: Users, CORS, Sessions

Manage users, allowed cross-origin apps, and refresh sessions via admin-only routes.

GET/api/v1/admin/cors

List CORS origins

List allowed origins.

Auth

Admin refresh session cookie.

Headers

  • cookie: <refresh cookie>

Response

{
  "origins": [
    {
      "id": "origin_123",
      "origin": "https://app.example.com",
      "createdAt": "2025-01-01T12:00:00.000Z",
      "updatedAt": "2025-01-01T12:00:00.000Z"
    }
  ]
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbidden

Example request

curl https://way-my-auth-service.vercel.app/api/v1/admin/cors

Example response

{
  "origins": [
    {
      "id": "origin_123",
      "origin": "https://app.example.com",
      "createdAt": "2025-01-01T12:00:00.000Z",
      "updatedAt": "2025-01-01T12:00:00.000Z"
    }
  ]
}
POST/api/v1/admin/cors

Add CORS origin

Add an allowed origin (http/https).

Auth

Admin refresh session cookie.

Headers

  • content-type: application/json
  • cookie: <refresh cookie>

Request body

{
  "origin": "https://app.example.com"
}

Response

{
  "origin": {
    "id": "origin_123",
    "origin": "https://app.example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-01T12:00:00.000Z"
  }
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbiddeninvalid_inputinvalid_origin

Example request

curl -X POST https://way-my-auth-service.vercel.app/api/v1/admin/cors \
  -H 'content-type: application/json' \
  -d '{"origin":"https://app.example.com"}'

Example response

{
  "origin": {
    "id": "origin_123",
    "origin": "https://app.example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-01T12:00:00.000Z"
  }
}
DELETE/api/v1/admin/cors/:id

Remove CORS origin

Remove an allowed origin by id.

Auth

Admin refresh session cookie.

Headers

  • cookie: <refresh cookie>

Response

{
  "success": true
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbiddeninvalid_origin

Example request

curl -X DELETE https://way-my-auth-service.vercel.app/api/v1/admin/cors/origin_123

Example response

{
  "success": true
}
GET/api/v1/admin/users

List users

List enrolled users with pagination.

Auth

Admin refresh session cookie.

Headers

  • cookie: way_refresh=<admin_refresh_session_cookie>

Response

{
  "users": [
    {
      "id": "user_123",
      "email": "you@example.com",
      "createdAt": "2025-01-01T12:00:00.000Z",
      "updatedAt": "2025-01-01T12:00:00.000Z"
    }
  ],
  "currentPage": 1,
  "pageSize": 50,
  "totalCount": 1,
  "totalPages": 1
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbidden

Example request

curl https://way-my-auth-service.vercel.app/api/v1/admin/users

Example response

{
  "users": [
    {
      "id": "user_123",
      "email": "you@example.com",
      "createdAt": "2025-01-01T12:00:00.000Z",
      "updatedAt": "2025-01-01T12:00:00.000Z"
    }
  ],
  "currentPage": 1,
  "pageSize": 50,
  "totalCount": 1,
  "totalPages": 1
}
POST/api/v1/admin/users

Create user

Create a user credential directly from admin.

Auth

Admin refresh session cookie.

Headers

  • content-type: application/json
  • cookie: way_refresh=<admin_refresh_session_cookie>

Request body

{
  "email": "you@example.com",
  "password": "strong-password"
}

Response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-01T12:00:00.000Z"
  }
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbiddeninvalid_inputemail_taken

Example request

curl -X POST https://way-my-auth-service.vercel.app/api/v1/admin/users \
  -H 'content-type: application/json' \
  -d '{"email":"you@example.com","password":"strong-password"}'

Example response

{
  "user": {
    "id": "user_123",
    "email": "you@example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-01T12:00:00.000Z"
  }
}
PATCH/api/v1/admin/users/:id

Update user credentials

Update a user's email and/or password.

Auth

Admin refresh session cookie.

Headers

  • content-type: application/json
  • cookie: way_refresh=<admin_refresh_session_cookie>

Request body

{
  "email": "updated@example.com",
  "password": "new-strong-password"
}

Response

{
  "user": {
    "id": "user_123",
    "email": "updated@example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-02T12:00:00.000Z"
  }
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbiddeninvalid_inputinvalid_user_iduser_not_foundemail_taken

Example request

curl -X PATCH https://way-my-auth-service.vercel.app/api/v1/admin/users/user_123 \
  -H 'content-type: application/json' \
  -d '{"email":"updated@example.com"}'

Example response

{
  "user": {
    "id": "user_123",
    "email": "updated@example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-02T12:00:00.000Z"
  }
}
DELETE/api/v1/admin/users/:id

Delete user

Delete a user and cascade-delete their sessions.

Auth

Admin refresh session cookie.

Headers

  • cookie: way_refresh=<admin_refresh_session_cookie>

Response

{
  "success": true,
  "user": {
    "id": "user_123",
    "email": "you@example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-01T12:00:00.000Z"
  }
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbiddeninvalid_user_iduser_not_found

Example request

curl -X DELETE https://way-my-auth-service.vercel.app/api/v1/admin/users/user_123

Example response

{
  "success": true,
  "user": {
    "id": "user_123",
    "email": "you@example.com",
    "createdAt": "2025-01-01T12:00:00.000Z",
    "updatedAt": "2025-01-01T12:00:00.000Z"
  }
}
GET/api/v1/admin/sessions

List sessions

List refresh sessions with user metadata.

Auth

Admin refresh session cookie.

Headers

  • cookie: way_refresh=<admin_refresh_session_cookie>

Response

{
  "sessions": [
    {
      "id": "session_123",
      "user": {
        "id": "user_123",
        "email": "you@example.com"
      },
      "createdAt": "2025-01-01T12:00:00.000Z",
      "expiresAt": "2025-02-01T12:00:00.000Z",
      "revokedAt": null,
      "replacedBySessionId": null,
      "status": "active"
    }
  ]
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbidden

Example request

curl https://way-my-auth-service.vercel.app/api/v1/admin/sessions

Example response

{
  "sessions": [
    {
      "id": "session_123",
      "user": {
        "id": "user_123",
        "email": "you@example.com"
      },
      "createdAt": "2025-01-01T12:00:00.000Z",
      "expiresAt": "2025-02-01T12:00:00.000Z",
      "revokedAt": null,
      "replacedBySessionId": null,
      "status": "active"
    }
  ]
}
DELETE/api/v1/admin/sessions/:id

Revoke session

Revoke a refresh session by id.

Auth

Admin refresh session cookie.

Headers

  • cookie: way_refresh=<admin_refresh_session_cookie>

Response

{
  "session": {
    "id": "session_123",
    "user": {
      "id": "user_123",
      "email": "you@example.com"
    },
    "createdAt": "2025-01-01T12:00:00.000Z",
    "expiresAt": "2025-02-01T12:00:00.000Z",
    "revokedAt": "2025-01-05T12:00:00.000Z",
    "replacedBySessionId": null,
    "status": "revoked"
  }
}

Errors

missing_refresh_tokeninvalid_refresh_tokenforbiddeninvalid_session_idsession_not_found

Example request

curl -X DELETE https://way-my-auth-service.vercel.app/api/v1/admin/sessions/session_123

Example response

{
  "session": {
    "id": "session_123",
    "user": {
      "id": "user_123",
      "email": "you@example.com"
    },
    "createdAt": "2025-01-01T12:00:00.000Z",
    "expiresAt": "2025-02-01T12:00:00.000Z",
    "revokedAt": "2025-01-05T12:00:00.000Z",
    "replacedBySessionId": null,
    "status": "revoked"
  }
}

Admin Access

Admin endpoints require a valid refresh session cookie and an email present in the ADMIN_EMAILS allowlist. These routes are intended for managing CORS origins and reviewing active refresh sessions.

Admin emails

ADMIN_EMAILS is a comma-separated list. Only those users may call /api/v1/admin/* routes.

Debug Endpoint

Use GET /api/v1/_debug/cookie in development to confirm the refresh cookie is present. Returns 404 outside of development.

{
  "cookieName": "way_refresh",
  "present": true,
  "checkedAt": "2025-01-01T12:00:00.000Z"
}