Skip to main content

Authentication

BookWish uses JWT (JSON Web Tokens) for API authentication. The authentication system supports multiple methods including email/password, guest accounts, and social login integration.

Authentication Flow

sequenceDiagram
participant Client
participant API
participant Database

Client->>API: POST /auth/login
API->>Database: Verify credentials
Database-->>API: User data
API-->>Client: access_token + refresh_token
Client->>API: Authenticated request with Bearer token
API->>Database: Verify token & fetch user
Database-->>API: User data
API-->>Client: Protected resource

Obtaining Tokens

Guest Account

Create a temporary guest account for unauthenticated users:

Endpoint: POST /auth/guest

Request:

{
"device_id": "unique-device-identifier"
}

Response:

{
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"displayName": "Guest User",
"isGuest": true,
"tier": "free"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Email/Password Signup

Create a new user account:

Endpoint: POST /auth/signup

Request:

{
"email": "user@example.com",
"password": "securePassword123",
"display_name": "John Doe"
}

Validation Rules:

  • Email must be valid format
  • Password must be at least 8 characters
  • Display name must be 1-100 characters

Response:

{
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "user@example.com",
"displayName": "John Doe",
"isGuest": false,
"tier": "free"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Error Responses:

StatusErrorDescription
400ValidationErrorInvalid input format
409EmailConflictEmail already registered

Email/Password Login

Authenticate an existing user:

Endpoint: POST /auth/login

Request:

{
"email": "user@example.com",
"password": "securePassword123"
}

Response:

{
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "user@example.com",
"displayName": "John Doe",
"isGuest": false,
"tier": "free"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Error Responses:

StatusErrorDescription
400ValidationErrorInvalid input format
401InvalidCredentialsEmail or password incorrect

Migrate Guest to Full Account

Convert a guest account to a full account with email/password:

Endpoint: POST /auth/migrate-guest

Headers: Authorization: Bearer {access_token}

Request:

{
"email": "user@example.com",
"password": "securePassword123",
"display_name": "John Doe"
}

Response:

{
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "user@example.com",
"displayName": "John Doe",
"isGuest": false,
"tier": "free"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Error Responses:

StatusErrorDescription
400BadRequestUser is not a guest account
409EmailConflictEmail already in use

Using Tokens

Access Token

Include the access token in the Authorization header of all authenticated requests:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Example Request:

curl -X GET https://api.bookwish.app/users/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Token Payload

Access tokens contain the following claims:

{
"userId": "123e4567-e89b-12d3-a456-426614174000",
"isGuest": false,
"tier": "free",
"iat": 1702046400,
"exp": 1702047300
}

Note: User tier is always fetched fresh from the database on each request, not from the token cache.

Token Refresh

When an access token expires, use the refresh token to obtain new tokens without re-authentication:

Endpoint: POST /auth/refresh

Request:

{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Error Responses:

StatusErrorDescription
401InvalidTokenToken is invalid or expired
401InvalidTokenToken has been revoked

Token Expiration

Token TypeExpirationUse Case
Access Token15 minutesAPI requests
Refresh Token7 daysObtaining new access tokens

Logout

Revoke refresh tokens to log out:

Endpoint: POST /auth/logout

Headers: Authorization: Bearer {access_token}

Request:

{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response:

{
"success": true
}

Password Reset

Password reset functionality is planned but not yet implemented in the current API version.

Social Login

Social login integration (Google, Apple, etc.) is planned for future releases.

Optional Authentication

Some endpoints support optional authentication where the response varies based on authentication status. These endpoints accept the Authorization header but don't require it.

Example: Book details may show user-specific wishlist status if authenticated.

Security Best Practices

Client-Side

  1. Store tokens securely

    • Use secure storage (Keychain on iOS, EncryptedSharedPreferences on Android)
    • Never store tokens in localStorage on web (use httpOnly cookies or secure storage)
  2. Handle token expiration

    • Implement automatic token refresh before expiration
    • Handle 401 responses by refreshing tokens
  3. Revoke tokens on logout

    • Always call the logout endpoint
    • Clear local token storage

Server-Side

  1. JWT secrets are 32+ characters for cryptographic security
  2. Tokens are verified on every request with database user lookup
  3. Refresh tokens can be revoked for security (logout endpoint)

Common Authentication Errors

StatusErrorMessageSolution
401UnauthorizedMissing or invalid authorization headerInclude valid Bearer token
401UnauthorizedInvalid or expired tokenRefresh token or re-authenticate
401UnauthorizedUser not foundUser account may have been deleted

Example: Complete Authentication Flow

// 1. Login
const loginResponse = await fetch('https://api.bookwish.app/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'securePassword123'
})
});

const { access_token, refresh_token } = await loginResponse.json();

// 2. Store tokens securely
await secureStorage.set('access_token', access_token);
await secureStorage.set('refresh_token', refresh_token);

// 3. Make authenticated request
const response = await fetch('https://api.bookwish.app/users/me', {
headers: {
'Authorization': `Bearer ${access_token}`
}
});

// 4. Handle token expiration
if (response.status === 401) {
// Refresh token
const refreshResponse = await fetch('https://api.bookwish.app/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token })
});

const newTokens = await refreshResponse.json();
await secureStorage.set('access_token', newTokens.access_token);
await secureStorage.set('refresh_token', newTokens.refresh_token);

// Retry original request
// ...
}