Add User Authentication
Set up signup, login with JWT tokens, and protect your endpoints.
Add User Authentication
Set up user registration, login, and JWT-based endpoint protection. By the end, you will have a working auth system you can add to any project.
What you will build
- POST /api/auth/register — create a new user account - POST /api/auth/login — sign in and receive a JWT token - A reusable pattern for protecting any endpoint
Steps
Create the users table
In the Database screen, create a users table:
| Column | Type | Notes |
|--------|------|-------|
| id | Serial | Primary key |
| email | Text | Unique, required |
| password_digest | Text | Required |
| name | Text | Optional |
| role | Text | Default: 'user' |
| created_at | Timestamp | Default: now() |
Build the register endpoint
Create POST /api/auth/register:
1. Find One — Table: users, Where: { email: input.email }, Assign To: existing
2. If/Else — Condition: vars.existing != null
- True: Respond — Status: 409, Body: { error: 'Email already registered' }
3. Hash Password — Password: input.password, Salt Rounds: 12, Assign To: hashedPassword
4. Database Insert — Table: users, Data: { email: input.email, name: input.name, password_digest: vars.hashedPassword }, Returning: id, email, name, Assign To: user
5. Respond — Status: 201, Body: { data: vars.user }
Build the login endpoint
Create POST /api/auth/login:
1. Find One — Table: users, Where: { email: input.email }, Assign To: user
2. If/Else — Condition: vars.user == null
- True: Respond — Status: 401, Body: { error: 'Invalid credentials' }
3. Verify Password — Password: input.password, Hash: vars.user.password_digest, Assign To: isValid
4. If/Else — Condition: vars.isValid == false
- True: Respond — Status: 401, Body: { error: 'Invalid credentials' }
5. Generate Auth Token — User ID: vars.user.id, Assign To: token
6. Respond — Status: 200, Body: { token: vars.token }
Protect an endpoint
The usual production path is to set the endpoint's Authentication option to JWT in the endpoint editor. Spala then rejects missing or invalid tokens before the function runs and exposes decoded auth data to the function.
Use manual verification steps only when an endpoint intentionally accepts mixed public/authenticated traffic or needs to verify a secondary token. For that custom case, add these steps at the top of its function:
1. Set Variable — Name: authHeader, Value: headers.authorization
2. If/Else — Condition: vars.authHeader == null
- True: Respond — Status: 401, Body: { error: 'No token provided' }
3. Try/Catch
- Try: Verify JWT — Token: vars.authHeader | split(' ') | last, Secret: env.JWT_SECRET, Assign To: auth
- Catch: Respond — Status: 401, Body: { error: 'Invalid token' }
After this, vars.auth.userId and vars.auth.role are available in the rest of the function.
Add the JWT secret
Go to Settings > Environment Variables and add JWT_SECRET with a long, random string.
Test login
In the Playground, call POST /api/auth/register with an email and password. Then call POST /api/auth/login with the same credentials and confirm you get a token back.
Important
Never store plaintext passwords. Always use the Hash Password step with bcrypt before inserting into the database.
Build a REST API /guides/build-rest-api Create the endpoints you want to protect API Playground /features/playground Test your auth endpoints in the browser