Wire signup + signin into your product.
Provision an app from the console, hit signup, signin, and /me from a test client. By the end you have one working end-user authenticating against your app, with a JWT you can verify in your own backend.
Step 1
Create an app in the console
From console.productcraft.co/auth, hit + New app and fill in the Create new app dialog. Pick a slug — it appears in every Consumer-API URL for the rest of this guide.
Treat the slug as permanent: it's baked into every Consumer-API URL and into the iss / aud claims of every token the app issues, so renaming it later breaks outstanding tokens and every integration URL. Change the display name freely, but pick a slug you can live with. For this guide we'll use acme. After creation the console drops you on the app's Settings tab, which shows the slug and app id together (/acme · <app-id>) — that's where you copy them from later.
Every new app comes with three seeded end-user roles (owner, admin, member); new signups get member by default. Chapter 3 covers customising them.
Prefer the API? App creation is one call with a PAK (a pcft_live_* workspace API key minted in the console under Workspace → API keys → New API key):
curl https://api.auth.productcraft.co/v1/apps \
-H 'authorization: Bearer pcft_live_...' \
-H 'content-type: application/json' \
-d '{
"slug": "acme",
"display_name": "Acme",
"workspace_id": "<your-workspace-uuid>"
}'Step 2
Enable signup
New apps ship locked down: signup_enabled defaults to false, so POST /auth/signup returns 403 until you flip it on. Do that in the console under Settings → Auth config → Sign-in policy (the Signup enabled toggle, then Save sign-in policy), or with one API call.
curl -X PATCH https://api.auth.productcraft.co/v1/apps/<app-id>/auth-config \
-H 'authorization: Bearer pcft_live_...' \
-H 'content-type: application/json' \
-d '{ "signup_enabled": true }'The other defaults are sensible — leave them unless your product needs different behaviour:
signin_enabled(defaulttrue) — emergency switch: when false, signin returns 403 without revoking already-issued tokens.- Password policy —
password_min_length(default12),password_require_uppercase(defaulttrue),password_require_number(defaulttrue),password_require_symbol(defaultfalse). session_duration_minutes(default10080— 7 days) — how long a session and its refresh token live. Access tokens always expire after 1 hour.signup_default_role(defaultmember) — the role new signups get.enforce_app_permissions(defaultfalse) — when off, any valid token can call any Consumer-API route; when on, permission-gated routes check the user's role permissions. Keep this off until you've modelled your roles (Chapter 3).
Step 3
Sign up your first user
Public surface. No bearer needed. The password must pass the app's policy (by default 12+ characters with an uppercase letter and a number — violations return 400 with a message naming the failed rule). The response includes an access token + refresh token ready to use. Email is the primary contact and starts unverified — that's fine for the first call; Chapter 7 covers the verification flow.
curl https://api.auth.productcraft.co/acme/v1/auth/signup \
-H 'content-type: application/json' \
-d '{
"email": "ada@example.com",
"username": "ada",
"password": "CorrectHorse42Battery",
"display_name": "Ada Lovelace"
}'Response:
{
"access_token": "eyJhbGc... (1h TTL)",
"refresh_token": "eyJhbGc... (7d TTL by default)",
"token_type": "Bearer",
"expires_in": 3600
}Step 4
Sign in an existing user
Same response shape as signup. identifier accepts either the username or the account's primary email — secondary email contacts do not authenticate.
curl https://api.auth.productcraft.co/acme/v1/auth/signin \
-H 'content-type: application/json' \
-d '{
"identifier": "ada",
"password": "CorrectHorse42Battery"
}'Failure modes — 401 { statusCode:401, message:"Invalid credentials" } for a wrong password or an unknown identifier (Auth does not differentiate the two, to avoid a username-existence oracle). A suspended account returns 403 Account is suspended, an app with signin disabled returns 403, and too many failed attempts returns 429.
Step 5
Read the user's profile
With the access token from step 3 or 4, hit /me. This is the canonical 'who am I' endpoint.
curl https://api.auth.productcraft.co/acme/v1/me \
-H 'authorization: Bearer eyJhbGc...'{
"id": "648616c8-...",
"username": "ada",
"display_name": "Ada Lovelace",
"role": "member",
"joined_at": "2026-05-11T11:39:37Z",
"created_at": "2026-05-11T11:39:35Z",
"email": "ada@example.com",
"email_verified_at": null
}role is the user's role name on this app — by default signup_default_role (which defaults to member). To get the flat permission set bound to that role, hit GET /me/permissions — that resolves through a 60-second cache, so role-permission edits propagate within a minute.
Step 6
Refresh the access token
Access tokens expire after 1h. Use the refresh token to mint a fresh pair without re-asking for credentials. The refresh token is rotated on every call — the previous one stops working immediately, and re-using it after a short grace window (60s, to absorb network retries) is treated as theft and revokes the whole session.
curl https://api.auth.productcraft.co/acme/v1/auth/refresh \
-H 'content-type: application/json' \
-d '{ "refresh_token": "eyJhbGc... (the previous refresh)" }'Step 7
Sign out
POST /auth/logout with the refresh token, or DELETE /me/sessions/:id with the bearer. Both revoke the session — the Consumer API rejects the access token immediately (401 'Session has been revoked'). Only your own services doing local JWKS verification keep accepting the JWT until its TTL.
curl https://api.auth.productcraft.co/acme/v1/auth/logout \
-H 'content-type: application/json' \
-d '{ "refresh_token": "eyJhbGc..." }'What's next
You have a working Auth integration
From here:
- Chapter 3 — bind permissions to roles so your routes can say “only users with
billing.readmay call this.” - Chapter 6 — verify the JWT in your own backend, not by calling /me on every request.
- Chapter 7 — wire up email verification so unverified emails don't accumulate forever.
- The full API reference at /docs/auth/api-reference has every endpoint, including the ones we didn't touch here (contact management, session list, activity log, tenant switching).