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