Skip to main content

Overview

BunShip implements a stateful JWT authentication system. Short-lived access tokens (15 minutes) authorize API requests, while long-lived refresh tokens (7 days) are backed by database sessions that can be individually revoked. Two-factor authentication, account lockout, and API key auth are included out of the box.

Auth Flow

1

Register

The user submits their email, password, and name. BunShip validates the password against strength rules, hashes it with Argon2id, creates the user record, and sends a verification email.
2

Verify email

The user clicks the link in their verification email. The API marks emailVerified with the current timestamp.
3

Login

The user submits their email and password. On success, the API returns an access token, a refresh token, and basic user info.
4

Make authenticated requests

Include the access token in the Authorization header of subsequent requests.
5

Refresh when the access token expires

When the access token expires (after 15 minutes), call the refresh endpoint to get a new pair. The old refresh token is rotated — each refresh token can only be used once.

JWT Structure

BunShip uses two separate JWT secrets and the jose library for signing and verification.
Payload:
Use different values for JWT_SECRET and JWT_REFRESH_SECRET. Generate them with openssl rand -hex 32.

Session Management

Every login creates a database-backed session record. Sessions store the hashed refresh token, the client’s user agent, IP address, and an expiration timestamp.
This gives users visibility into where they are logged in and the ability to revoke specific sessions:
  • List sessionsGET /api/v1/users/sessions
  • Revoke one sessionDELETE /api/v1/users/sessions/:id
  • Revoke all sessionsDELETE /api/v1/users/sessions (logs out everywhere)
The maximum number of concurrent sessions per user defaults to 5 and is configurable via featuresConfig.auth.maxSessionsPerUser.

Two-Factor Authentication

BunShip supports TOTP-based two-factor authentication (compatible with Google Authenticator, Authy, 1Password, and similar apps) plus single-use backup codes.

Setup Flow

1

Request 2FA setup

The user provides their current password. The API generates a TOTP secret, a QR code URI, and 10 backup codes.
Backup codes are shown only once. Instruct users to store them in a safe location.
2

Verify with a TOTP code

The user enters a 6-digit code from their authenticator app. This confirms the secret was saved correctly and activates 2FA on the account.
3

Login now requires a second factor

Subsequent login attempts return a requiresTwoFactor: true error if the twoFactorCode field is omitted:

TOTP Parameters

Backup Codes

  • 10 codes generated per setup
  • Each code is an 8-character hexadecimal string
  • Codes are hashed with SHA-256 before storage (the plaintext is never persisted)
  • Each code can only be used once — the usedAt timestamp is set on consumption
  • Re-running 2FA setup regenerates all backup codes and invalidates the previous set

Password Policies

BunShip validates password strength at registration and password reset. The rules are defined in featuresConfig.auth.password: Passwords are hashed using Argon2id with the following parameters (via Bun’s native Bun.password API or the argon2 library):
  • Memory: 65536 KB
  • Iterations: 3
  • Parallelism: 4
BunShip never stores plaintext passwords. The passwordHash field in the users table contains only the Argon2id hash output.

Account Lockout

To protect against brute-force attacks, BunShip tracks failed login attempts and temporarily locks accounts. The lockout logic in auth.service.ts:
The login endpoint uses constant-time password verification even when the user does not exist, preventing timing-based user enumeration.

API Key Authentication

API keys provide an alternative to JWT tokens for server-to-server integrations and automated scripts. Keys are scoped to an organization and carry explicit permission scopes.

How API Keys Work

  1. A team member with api-keys:create permission generates a key through the API or dashboard
  2. The full key is shown once (format: bsk_live_...); only the prefix and hash are stored
  3. The caller includes the key in the Authorization header: Bearer bsk_live_...
  4. The API resolves the key to an organization and checks that the key’s scopes grant the required permission

Available Scopes

Key Properties

Limits

See API Keys for usage details and the API Reference for endpoint documentation.

Auth Middleware Reference

BunShip provides two auth middleware variants:

Supported Auth Methods

Email + Password

Traditional registration and login with password strength validation and Argon2id hashing.

Magic Link

Passwordless login via a one-time link sent to the user’s email address.

Google OAuth

Social login with Google. Requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables.

GitHub OAuth

Social login with GitHub. Requires GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.
Each method can be toggled independently through featuresConfig.auth: