Documentation Index Fetch the complete documentation index at: https://docs.jethings.com/llms.txt
Use this file to discover all available pages before exploring further.
Authentication API
The Authentication API provides secure user authentication, email verification, password management, and token handling for your applications.
All endpoints use the base URL: https://jethings-backend.fly.dev (production) or http://localhost:3000 (development)
🔐 Authentication Flow
Sign Up
Create account with email verification
Verify Email
Enter OTP sent to email
Sign In
Get access and refresh tokens
Make Requests
Use access token in Authorization header
Refresh Token
Get new tokens when access token expires
📝 User Registration
Sign Up
Create a new user account with email verification.
curl -X POST https://jethings-backend.fly.dev/auth/signup \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"phoneNumber": "+1234567890",
"password": "strongPass123",
"age": 28,
"description": "Software developer"
}'
Request Body:
Field Type Required Description
firstNamestring ✅ User’s first name lastNamestring ✅ User’s last name emailstring ✅ Valid email address (must be unique) phoneNumberstring ✅ Valid phone number (must be unique) passwordstring ✅ Minimum 6 characters agenumber ✅ Integer between 1-120 descriptionstring ❌ Optional user bio
Success Response (200):
{
"message" : "User created, check your email for verification code"
}
Error Responses:
409 Conflict: Email already exists
409 Conflict: Phone number already exists
400 Bad Request: Validation errors
Verify Email
Verify user email with the OTP sent during signup.
curl -X POST https://jethings-backend.fly.dev/auth/verify-email \
-H "Content-Type: application/json" \
-d '{"otp": "123456"}'
Request Body:
Field Type Required Description
otpstring ✅ 6-digit verification code
Success Response (200):
{
"message" : "Email verified successfully"
}
Error Responses:
400 Bad Request: Invalid or expired OTP
400 Bad Request: Too many attempts (max 3)
OTP expires after 10 minutes
Maximum 3 attempts per OTP
OTP is automatically deleted after successful verification
🔑 User Authentication
Sign In
Authenticate user and receive access/refresh tokens.
curl -X POST https://jethings-backend.fly.dev/auth/signin \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "strongPass123"
}'
Request Body:
Field Type Required Description
emailstring ✅ User’s email address passwordstring ✅ User’s password
Success Response (200):
{
"message" : "Signed in successfully" ,
"user" : {
"id" : "ck_123..." ,
"email" : "jane@example.com" ,
"firstName" : "Jane" ,
"lastName" : "Doe" ,
"roles" : [ "user" ]
},
"accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"refreshToken" : "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" ,
"expiresIn" : 900
}
Error Responses:
401 Unauthorized: Invalid credentials
401 Unauthorized: Email not verified
Access token expires in 15 minutes
Refresh token expires in 7 days
Store refresh token securely (httpOnly cookie recommended)
Get Current User
Retrieve current authenticated user’s profile.
curl -X GET https://jethings-backend.fly.dev/auth/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Success Response (200):
{
"id" : "ck_123..." ,
"email" : "jane@example.com" ,
"firstName" : "Jane" ,
"lastName" : "Doe" ,
"age" : 28 ,
"phoneNumber" : "+1234567890" ,
"avatarUrl" : "https://example.com/avatar.jpg" ,
"description" : "Software developer" ,
"roles" : [ "user" ],
"isEmailVerified" : true ,
"lastActivity" : "2024-01-15T10:30:00.000Z" ,
"createdAt" : "2024-01-01T00:00:00.000Z" ,
"updatedAt" : "2024-01-15T10:30:00.000Z"
}
Error Responses:
401 Unauthorized: Invalid or expired token
401 Unauthorized: User not found
401 Unauthorized: Account deactivated
🔄 Refresh Token Flow
Overview
Jethings Backend implements a secure refresh token authentication system that provides persistent, secure user sessions without compromising security.
Token Types:
Access Token : Short-lived JWT (15 minutes) used to authenticate API requests
Refresh Token : Long-lived token (7 days) used to obtain new access tokens
Key Features:
Automatic token refresh via backend middleware
Manual token refresh option
Token rotation on each refresh
Secure token revocation
Authentication Flow Diagram
Storing Tokens
Store tokens securely on the client. Common approaches:
Web Applications:
localStorage - Persists across browser sessions
sessionStorage - Persists only for current session
httpOnly cookies (recommended for refresh tokens)
React Context/State Management
Mobile Applications:
Secure storage (Keychain on iOS, Keystore on Android)
Encrypted shared preferences
Example: Storing tokens after login
const loginResponse = await fetch ( 'https://jethings-backend.fly.dev/auth/signin' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email , password }),
});
const data = await loginResponse . json ();
// Store tokens securely
localStorage . setItem ( 'accessToken' , data . accessToken );
localStorage . setItem ( 'refreshToken' , data . refreshToken );
localStorage . setItem ( 'tokenExpiresAt' , Date . now () + ( data . expiresIn * 1000 ));
Option 1: Automatic Token Refresh (Recommended)
The backend middleware automatically refreshes expired access tokens when both tokens are included in the request headers. This is the simplest and recommended approach.
How it works:
Include both Authorization: Bearer <accessToken> and x-refresh-token: <refreshToken> headers
If the access token is expired, the backend automatically refreshes it
New tokens are returned in response headers
Update your stored tokens with the new values
Implementation Example:
async function makeAuthenticatedRequest ( url : string , options : RequestInit = {}) {
const accessToken = localStorage . getItem ( 'accessToken' );
const refreshToken = localStorage . getItem ( 'refreshToken' );
const response = await fetch ( url , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ accessToken } ` ,
'x-refresh-token' : refreshToken || '' ,
'Content-Type' : 'application/json' ,
},
});
// Check if tokens were refreshed
const newAccessToken = response . headers . get ( 'x-new-access-token' );
const newRefreshToken = response . headers . get ( 'x-new-refresh-token' );
const tokenRefreshed = response . headers . get ( 'x-token-refreshed' );
if ( tokenRefreshed === 'true' && newAccessToken && newRefreshToken ) {
// Update stored tokens
localStorage . setItem ( 'accessToken' , newAccessToken );
localStorage . setItem ( 'refreshToken' , newRefreshToken );
// Update expires time if provided
const expiresIn = response . headers . get ( 'x-token-expires-in' );
if ( expiresIn ) {
localStorage . setItem ( 'tokenExpiresAt' , Date . now () + ( parseInt ( expiresIn ) * 1000 ));
}
}
return response ;
}
// Usage
const data = await makeAuthenticatedRequest ( 'https://jethings-backend.fly.dev/api/protected-endpoint' , {
method: 'GET' ,
});
Response Headers (Automatic Refresh):
When automatic token refresh occurs, the backend responds with:
Header Description
x-new-access-tokenNew access token (if refreshed) x-new-refresh-tokenNew refresh token (if refreshed) x-token-refreshed"true" if tokens were refreshedx-token-expires-inToken expiration time in seconds
Option 2: Manual Token Refresh
Implement your own refresh logic before making requests. This gives you more control over when tokens are refreshed.
Implementation Example:
async function ensureValidToken () : Promise < string > {
const accessToken = localStorage . getItem ( 'accessToken' );
const refreshToken = localStorage . getItem ( 'refreshToken' );
const expiresAt = localStorage . getItem ( 'tokenExpiresAt' );
// Check if token is expired or about to expire (within 1 minute)
if ( ! accessToken || ! expiresAt || Date . now () >= parseInt ( expiresAt ) - 60000 ) {
if ( ! refreshToken ) {
// Redirect to login
window . location . href = '/login' ;
throw new Error ( 'No valid tokens' );
}
// Refresh the token
const response = await fetch ( 'https://jethings-backend.fly.dev/auth/refresh-token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ refreshToken }),
});
if ( ! response . ok ) {
// Refresh token is invalid, redirect to login
localStorage . removeItem ( 'accessToken' );
localStorage . removeItem ( 'refreshToken' );
localStorage . removeItem ( 'tokenExpiresAt' );
window . location . href = '/login' ;
throw new Error ( 'Token refresh failed' );
}
const data = await response . json ();
// Update stored tokens
localStorage . setItem ( 'accessToken' , data . accessToken );
localStorage . setItem ( 'refreshToken' , data . refreshToken );
localStorage . setItem ( 'tokenExpiresAt' , Date . now () + ( data . expiresIn * 1000 ));
return data . accessToken ;
}
return accessToken ;
}
async function makeAuthenticatedRequest ( url : string , options : RequestInit = {}) {
const accessToken = await ensureValidToken ();
return fetch ( url , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json' ,
},
});
}
React Hook Example
For React applications, here’s a custom hook for managing authentication:
import { useState , useEffect , useCallback } from 'react' ;
function useAuth () {
const [ accessToken , setAccessToken ] = useState < string | null >(
localStorage . getItem ( 'accessToken' )
);
const [ refreshToken , setRefreshToken ] = useState < string | null >(
localStorage . getItem ( 'refreshToken' )
);
const refreshTokens = useCallback ( async () => {
if ( ! refreshToken ) return false ;
try {
const response = await fetch ( 'https://jethings-backend.fly.dev/auth/refresh-token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ refreshToken }),
});
if ( ! response . ok ) {
// Clear tokens and redirect to login
setAccessToken ( null );
setRefreshToken ( null );
localStorage . removeItem ( 'accessToken' );
localStorage . removeItem ( 'refreshToken' );
return false ;
}
const data = await response . json ();
setAccessToken ( data . accessToken );
setRefreshToken ( data . refreshToken );
localStorage . setItem ( 'accessToken' , data . accessToken );
localStorage . setItem ( 'refreshToken' , data . refreshToken );
return true ;
} catch ( error ) {
console . error ( 'Token refresh failed:' , error );
return false ;
}
}, [ refreshToken ]);
const makeRequest = useCallback ( async ( url : string , options : RequestInit = {}) => {
const response = await fetch ( url , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ accessToken } ` ,
'x-refresh-token' : refreshToken || '' ,
'Content-Type' : 'application/json' ,
},
});
// Handle automatic token refresh
const newAccessToken = response . headers . get ( 'x-new-access-token' );
const newRefreshToken = response . headers . get ( 'x-new-refresh-token' );
const tokenRefreshed = response . headers . get ( 'x-token-refreshed' );
if ( tokenRefreshed === 'true' && newAccessToken && newRefreshToken ) {
setAccessToken ( newAccessToken );
setRefreshToken ( newRefreshToken );
localStorage . setItem ( 'accessToken' , newAccessToken );
localStorage . setItem ( 'refreshToken' , newRefreshToken );
}
return response ;
}, [ accessToken , refreshToken ]);
return { accessToken , refreshToken , refreshTokens , makeRequest };
}
Axios Interceptor Example
For applications using Axios, you can use interceptors to handle token refresh automatically:
import axios from 'axios' ;
// Create axios instance
const apiClient = axios . create ({
baseURL: 'https://jethings-backend.fly.dev' ,
});
// Request interceptor to add tokens
apiClient . interceptors . request . use (
( config ) => {
const accessToken = localStorage . getItem ( 'accessToken' );
const refreshToken = localStorage . getItem ( 'refreshToken' );
if ( accessToken ) {
config . headers . Authorization = `Bearer ${ accessToken } ` ;
}
if ( refreshToken ) {
config . headers [ 'x-refresh-token' ] = refreshToken ;
}
return config ;
},
( error ) => Promise . reject ( error )
);
// Response interceptor to handle token refresh
apiClient . interceptors . response . use (
( response ) => {
const newAccessToken = response . headers [ 'x-new-access-token' ];
const newRefreshToken = response . headers [ 'x-new-refresh-token' ];
const tokenRefreshed = response . headers [ 'x-token-refreshed' ];
if ( tokenRefreshed === 'true' && newAccessToken && newRefreshToken ) {
localStorage . setItem ( 'accessToken' , newAccessToken );
localStorage . setItem ( 'refreshToken' , newRefreshToken );
}
return response ;
},
async ( error ) => {
const originalRequest = error . config ;
// If 401 and we haven't already tried to refresh
if ( error . response ?. status === 401 && ! originalRequest . _retry ) {
originalRequest . _retry = true ;
const refreshToken = localStorage . getItem ( 'refreshToken' );
if ( ! refreshToken ) {
// Redirect to login
window . location . href = '/login' ;
return Promise . reject ( error );
}
try {
const response = await axios . post ( 'https://jethings-backend.fly.dev/auth/refresh-token' , {
refreshToken ,
});
const { accessToken , refreshToken : newRefreshToken } = response . data ;
localStorage . setItem ( 'accessToken' , accessToken );
localStorage . setItem ( 'refreshToken' , newRefreshToken );
// Retry original request with new token
originalRequest . headers . Authorization = `Bearer ${ accessToken } ` ;
return apiClient ( originalRequest );
} catch ( refreshError ) {
// Refresh failed, redirect to login
localStorage . removeItem ( 'accessToken' );
localStorage . removeItem ( 'refreshToken' );
window . location . href = '/login' ;
return Promise . reject ( refreshError );
}
}
return Promise . reject ( error );
}
);
Sign Out / Token Revocation
Always revoke refresh tokens when the user signs out:
async function signOut () {
const refreshToken = localStorage . getItem ( 'refreshToken' );
if ( refreshToken ) {
try {
await fetch ( 'https://jethings-backend.fly.dev/auth/revoke-token' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ localStorage . getItem ( 'accessToken' ) } ` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({ refreshToken }),
});
} catch ( error ) {
console . error ( 'Error revoking token:' , error );
}
}
// Clear local storage
localStorage . removeItem ( 'accessToken' );
localStorage . removeItem ( 'refreshToken' );
localStorage . removeItem ( 'tokenExpiresAt' );
// Redirect to login
window . location . href = '/login' ;
}
Security Best Practices
Important Security Considerations:
✅ Never expose refresh tokens in URLs, client-side logs, or error messages
✅ Use HTTPS in production to protect tokens in transit
✅ Consider httpOnly cookies for refresh tokens in web applications
✅ Implement token rotation - always use new refresh tokens when received
✅ Handle token expiration gracefully - redirect to login when refresh fails
✅ Store tokens securely - use secure storage mechanisms appropriate for your platform
✅ Revoke tokens on logout - always call the revoke endpoint when users sign out
✅ Clear tokens on errors - remove stored tokens if refresh fails
🔄 Token Management
Refresh Token
Get new access and refresh tokens when the current access token expires.
curl -X POST https://jethings-backend.fly.dev/auth/refresh-token \
-H "Content-Type: application/json" \
-d '{"refreshToken": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"}'
Request Body:
Field Type Required Description
refreshTokenstring ✅ Valid refresh token
Success Response (200):
{
"message" : "Tokens refreshed successfully" ,
"accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"refreshToken" : "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1" ,
"expiresIn" : 900
}
Error Responses:
401 Unauthorized: Invalid or expired refresh token
Revoke Token
Revoke a specific refresh token (logout from one device).
curl -X POST https://jethings-backend.fly.dev/auth/revoke-token \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"}'
Success Response (200):
{
"message" : "Refresh token revoked successfully"
}
Revoke All Tokens
Revoke all refresh tokens for the current user (logout from all devices).
curl -X POST https://jethings-backend.fly.dev/auth/revoke-all-tokens \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Success Response (200):
{
"message" : "All refresh tokens revoked successfully"
}
Logout
Alias for revoke-all-tokens (logout from all devices).
curl -X POST https://jethings-backend.fly.dev/auth/logout \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Success Response (200):
{
"message" : "All refresh tokens revoked successfully"
}
🔒 Password Management
Request Password Reset
Request a password reset OTP to be sent to user’s email.
curl -X POST https://jethings-backend.fly.dev/auth/request-password-reset \
-H "Content-Type: application/json" \
-d '{"email": "jane@example.com"}'
Request Body:
Field Type Required Description
emailstring ✅ User’s email address
Success Response (200):
{
"message" : "If that email exists, a reset code was sent"
}
Always returns this message to prevent email enumeration attacks.
Verify Password Reset
Reset password using the OTP sent to user’s email.
curl -X POST https://jethings-backend.fly.dev/auth/verify-password-reset \
-H "Content-Type: application/json" \
-d '{
"otp": "123456",
"newPassword": "newStrongPass456"
}'
Request Body:
Field Type Required Description
otpstring ✅ 6-digit reset code newPasswordstring ✅ New password (min 6 characters)
Success Response (200):
{
"message" : "Password reset successful"
}
Error Responses:
400 Bad Request: Invalid or expired OTP
400 Bad Request: Too many attempts (max 3)
🛠️ Development Endpoints
Create Super Admin (Development Only)
Create a super admin account for development purposes.
This endpoint is only available when NODE_ENV=development
curl -X POST https://jethings-backend.fly.dev/auth/dev/super-admin \
-H "Content-Type: application/json" \
-d '{
"firstName": "Super",
"lastName": "Admin",
"email": "superadmin@example.com",
"password": "superSecurePassword123",
"phoneNumber": "+1234567890",
"age": 35,
"description": "Development super administrator"
}'
Request Body:
Field Type Required Description
firstNamestring ✅ Super admin’s first name lastNamestring ✅ Super admin’s last name emailstring ✅ Valid email address (must be unique) passwordstring ✅ Minimum 6 characters phoneNumberstring ✅ Valid phone number (must be unique) agenumber ✅ Integer between 1-120 descriptionstring ❌ Optional description
Success Response (200):
{
"message" : "Super admin created successfully (Development Only)" ,
"admin" : {
"id" : "ck_789..." ,
"email" : "superadmin@example.com" ,
"firstName" : "Super" ,
"lastName" : "Admin" ,
"age" : 35 ,
"phoneNumber" : "+1234567890" ,
"avatarUrl" : null ,
"description" : "Development super administrator" ,
"roles" : [ "super_admin" ],
"isEmailVerified" : true ,
"isActive" : true ,
"lastActivity" : null ,
"createdAt" : "2024-01-15T12:00:00.000Z" ,
"updatedAt" : "2024-01-15T12:00:00.000Z" ,
"isAdmin" : false ,
"isSuperAdmin" : true
}
}
Error Responses:
403 Forbidden: Endpoint only available in development
409 Conflict: Email already exists
409 Conflict: Phone number already exists
🔧 Error Handling
Common Error Codes
Status Code Description Common Causes
400 Bad RequestInvalid request data Validation errors, malformed JSON 401 UnauthorizedAuthentication required Missing/invalid token, expired token 403 ForbiddenInsufficient permissions Wrong role, development-only endpoint 404 Not FoundResource not found Invalid endpoint, missing resource 409 ConflictResource conflict Email/phone already exists 500 Internal Server ErrorServer error Database issues, unexpected errors
{
"message" : "Error description" ,
"statusCode" : 400
}
Handling Errors in Your Code
try {
const response = await fetch ( 'https://jethings-backend.fly.dev/auth/signin' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email , password })
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( error . message || 'Authentication failed' );
}
const data = await response . json ();
// Handle success
} catch ( error ) {
console . error ( 'Auth error:' , error . message );
// Handle error
}
🚀 Next Steps
Now that you understand the Authentication API, explore the Users API for user management features, or check out our Flutter Integration Guide for mobile development.
Users API User management, search, and admin operations
Flutter Integration Complete Flutter implementation guide