From 8b92ed39c06a092f701e20836b434bca9be18f9e Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Sun, 5 Jul 2026 20:17:51 -0400 Subject: [PATCH] docs: add direct HTTP quickstart Add docs/direct-http-quickstart.md: an end-to-end curl walkthrough of the direct HTTP integration path (login -> ephemeral token -> email OTP -> access/refresh tokens -> authenticated call -> refresh). Request and response shapes are taken from the route schemas, including that the refresh token is sent in the Authorization header and that refresh rotates the token. Link it from the README direct-HTTP entry and add it to the docs gitignore allowlist. --- .gitignore | 1 + README.md | 3 +- docs/direct-http-quickstart.md | 160 +++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 docs/direct-http-quickstart.md diff --git a/.gitignore b/.gitignore index ca07ec6..2d7256c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ docker-data/ docs/* !docs/admin-operations.md !docs/architecture.md +!docs/direct-http-quickstart.md !docs/ecosystem.md !docs/oauth.md !docs/production-operations.md diff --git a/README.md b/README.md index 1fba14b..89c3d79 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,8 @@ If you want hosted auth with a full control plane and operational support, use t - Postgres for persistence - Your application integrates via: - SeamlessAuth server SDK (recommended) - - Direct HTTP APIs (advanced) + - Direct HTTP APIs (advanced) - see [docs/direct-http-quickstart.md](./docs/direct-http-quickstart.md) + for an end-to-end `curl` login, token, and refresh walkthrough ## Bearer Token Contract diff --git a/docs/direct-http-quickstart.md b/docs/direct-http-quickstart.md new file mode 100644 index 0000000..ef645f8 --- /dev/null +++ b/docs/direct-http-quickstart.md @@ -0,0 +1,160 @@ +# Direct HTTP Quickstart + +This walks through a full passwordless login with plain `curl`, without the SeamlessAuth SDKs. +It is the "Direct HTTP APIs (advanced)" path: you call this API directly and take custody of the +returned tokens yourself. For browser apps, the recommended path is a trusted server adapter that +holds token custody for you (see [Deployment Topology](./architecture.md#deployment-topology)). + +The example uses **email OTP** because it is the easiest flow to drive from a shell. The same +shape applies to phone OTP (swap `email` for `phone` endpoints). + +Assumptions: + +- The API is running at `http://localhost:5312`. +- `email_otp` is in `LOGIN_METHODS`. +- A verified user already exists for `alice@example.com`. To create one from scratch, use + `POST /registration/register` followed by `POST /otp/verify-email-otp` (same token shape as + steps 3-4 below). + +Set a base URL for the snippets: + +```bash +BASE=http://localhost:5312 +``` + +## 1. Start a login + +`POST /login` with an identifier (email or phone). It returns a short-lived **ephemeral token** +plus the login methods allowed for this user and device. + +```bash +curl -sS -X POST "$BASE/login" \ + -H 'Content-Type: application/json' \ + -d '{"identifier":"alice@example.com"}' +``` + +```json +{ + "message": "Login continued", + "token": "", + "sub": "user-uuid", + "identifierType": "email", + "loginMethods": ["email_otp", "passkey"], + "ttl": 300 +} +``` + +Capture the ephemeral token: + +```bash +EPHEMERAL= +``` + +## 2. Request the login OTP + +`GET /otp/generate-login-email-otp` with the ephemeral token as a Bearer credential sends the code +to the user's email. + +To read the code back in a headless flow, add `x-seamless-auth-delivery-mode: external`. In +development this returns the code directly in a `delivery` payload. **In production, external +delivery also requires a valid `x-seamless-service-token` from a trusted server adapter**; without +it the code is only sent through the configured messaging provider. + +```bash +curl -sS "$BASE/otp/generate-login-email-otp" \ + -H "Authorization: Bearer $EPHEMERAL" \ + -H 'x-seamless-auth-delivery-mode: external' +``` + +```json +{ + "message": "OTP sent", + "delivery": { "kind": "otp_email", "to": "alice@example.com", "token": "ABCDEF" } +} +``` + +Without external delivery mode, the response is just `{ "message": "OTP sent" }` and you read the +code from the email itself. + +## 3. Verify the OTP and receive tokens + +`POST /otp/verify-login-email-otp` with the ephemeral token as the Bearer credential and the code +in the body. On success it returns the **access token** and **refresh token**. + +```bash +curl -sS -X POST "$BASE/otp/verify-login-email-otp" \ + -H "Authorization: Bearer $EPHEMERAL" \ + -H 'Content-Type: application/json' \ + -d '{"verificationToken":"ABCDEF"}' +``` + +```json +{ + "message": "Login successful", + "token": "", + "refreshToken": "", + "sub": "user-uuid", + "roles": ["user"], + "email": "alice@example.com", + "ttl": 1800, + "refreshTtl": 3600 +} +``` + +```bash +ACCESS= +REFRESH= +``` + +## 4. Call an authenticated endpoint + +Present the access token as a Bearer credential. + +```bash +curl -sS "$BASE/users/me" -H "Authorization: Bearer $ACCESS" +``` + +```json +{ + "user": { + "id": "user-uuid", + "email": "alice@example.com", + "phone": null, + "roles": ["user"] + }, + "credentials": [] +} +``` + +## 5. Refresh the session + +`POST /refresh` takes the **refresh token in the `Authorization` header** (not the body) and +returns a new access token and a new refresh token. + +```bash +curl -sS -X POST "$BASE/refresh" -H "Authorization: Bearer $REFRESH" +``` + +```json +{ + "message": "Token refreshed", + "token": "", + "refreshToken": "", + "sub": "user-uuid", + "ttl": 1800, + "refreshTtl": 3600 +} +``` + +Refresh tokens are **rotated**: the token you just sent is invalidated and replaced by the new +one. Reusing an old refresh token is treated as a compromise signal and revokes the session chain, +so always store the newest `refreshToken` from each refresh. + +## Token custody notes + +- The access token is a signed JWT; verify it against the JWKS at + `GET /.well-known/jwks.json` (RS256). +- This API returns tokens only in JSON bodies and never sets cookies. In a browser context, keep + the refresh token out of JavaScript-accessible storage by terminating the session in a trusted + server adapter or backend instead of integrating directly. See + [Deployment Topology](./architecture.md#deployment-topology).