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:
| Status | Error | Description |
|---|---|---|
| 400 | ValidationError | Invalid input format |
| 409 | EmailConflict | Email 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:
| Status | Error | Description |
|---|---|---|
| 400 | ValidationError | Invalid input format |
| 401 | InvalidCredentials | Email 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:
| Status | Error | Description |
|---|---|---|
| 400 | BadRequest | User is not a guest account |
| 409 | EmailConflict | Email 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:
| Status | Error | Description |
|---|---|---|
| 401 | InvalidToken | Token is invalid or expired |
| 401 | InvalidToken | Token has been revoked |
Token Expiration
| Token Type | Expiration | Use Case |
|---|---|---|
| Access Token | 15 minutes | API requests |
| Refresh Token | 7 days | Obtaining 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
-
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)
-
Handle token expiration
- Implement automatic token refresh before expiration
- Handle 401 responses by refreshing tokens
-
Revoke tokens on logout
- Always call the logout endpoint
- Clear local token storage
Server-Side
- JWT secrets are 32+ characters for cryptographic security
- Tokens are verified on every request with database user lookup
- Refresh tokens can be revoked for security (logout endpoint)
Common Authentication Errors
| Status | Error | Message | Solution |
|---|---|---|---|
| 401 | Unauthorized | Missing or invalid authorization header | Include valid Bearer token |
| 401 | Unauthorized | Invalid or expired token | Refresh token or re-authenticate |
| 401 | Unauthorized | User not found | User 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
// ...
}