# Auth and Authorization
## What this builds
Full JWT-based authentication: login page, register, session validation, role-based access control, protected routes, and logout. Works with PostgreSQL or Sekejap for user storage.
---
## JWT Credential
Create a `jwt_signing_key` credential in the Credentials UI. Fields:
```json
{
"algorithm": "HS256",
"secret": "your-signing-secret",
"auth_roles": ["user", "admin"],
"auth_redirect": "/auth/login",
"auth_forbidden_redirect": "/home"
}
```
| `algorithm` | `HS256` / `HS384` / `HS512` (symmetric) or `RS256` / `RS384` / `RS512` / `ES256` / `ES384` (asymmetric) |
| `secret` | Signing key for HS* algorithms |
| `auth_roles` | Roles registered for this credential — used to populate the **Required Role** checkboxes in webhook nodes. Defines what values are valid in the JWT `role` claim. |
| `auth_redirect` | Where to redirect on 401 (browser navigation only — `Sec-Fetch-Mode: navigate`) |
| `auth_forbidden_redirect` | Where to redirect on 403 (browser navigation only) |
`auth_redirect` and `auth_forbidden_redirect` trigger only on browser page navigation. API/fetch calls always receive JSON 401/403.
### `--auth-required-role` behaviour
- **One or more roles selected** → JWT `role` claim must match one of the selected roles or request is rejected with 403.
- **No roles selected (empty)** → any holder of a valid JWT may access — role is not checked. Use this for "authenticated but unrestricted" routes.
---
## Pipelines
1. `GET /auth/login` → render login page
2. `POST /auth/login` → validate credentials → issue JWT cookie → redirect
3. `GET /auth/register` → render register page
4. `POST /auth/register` → hash password → create user → redirect
5. `GET /auth/logout` → clear cookie → redirect to login
6. `GET /dashboard` → JWT auto-verify → load user → render protected page
7. `GET /admin/*` → JWT auto-verify + role check → serve or redirect/403
---
### auth-login-page — render login form
```
| trigger.webhook --path /auth/login --method GET
| web.response --template pages/auth-login.tsx
```
### auth-login-submit — authenticate and issue token
```
| trigger.webhook --path /auth/login --method POST
| pg.query --credential main-db --params-expr "[input.username]" \
-- "SELECT id::text, username, role FROM users WHERE username = $1 LIMIT 1"
| script -- "const user = input.rows?.[0]; if (!user) return { ok: false, error: 'invalid credentials', __status: 401 }; return { id: user.id, username: user.username, roles: [user.role] }"
| auth.token.create --credential my-jwt --claim sub=$.id --claim username=$.username:public --claim roles=$.roles:public --expires-in 86400
| web.response --location /dashboard --set-cookie name=session,value=$.access_token,http-only,max-age=86400,path=/
```
> **Note:** `roles` must be an array in the JWT claim — wrap a single DB `role` string with `[user.role]`. If your schema already returns an array (junction table, `text[]` column), use it directly.
### auth-register-page — render register form
```
| trigger.webhook --path /auth/register --method GET
| web.response --template pages/auth-register.tsx
```
### auth-register-submit — create new user
```
| trigger.webhook --path /auth/register --method POST