Overview
The Vaniyk Empire API uses Supabase Auth for authentication, which provides secure JWT (JSON Web Token) based authentication. All protected endpoints require a valid access token in the Authorization header.
The API uses a dual-database architecture: Supabase manages authentication and sessions, while MongoDB stores user profiles and application data.
Authentication Flow
User Signs Up or Logs In
The user provides credentials (email and password) to either /api/auth/signup or /api/auth/login.
Supabase Creates Session
Supabase Auth validates credentials and generates a session with an access_token and refresh_token.
API Creates User Record
For signups, the API creates a corresponding user record in MongoDB linked to the Supabase user ID.
Client Stores Tokens
The client application stores the access token (and optionally the refresh token) securely.
Client Includes Token in Requests
The client includes the access token in the Authorization header for all authenticated requests.
API Validates Token
The authentication middleware validates the token with Supabase and attaches user data to the request.
Creating an Account
Signup Endpoint
POST /api/auth/signup
Content-Type : application/json
{
"email" : "[email protected] " ,
"password" : "SecurePassword123!" ,
"name" : "John Doe"
}
curl -X POST https://api.vaniykempire.com/api/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected] ",
"password": "SecurePassword123!",
"name": "John Doe"
}'
Response
{
"message" : "User created successfully" ,
"user" : {
"id" : "65f7b3c8e1234567890abcde" ,
"email" : "[email protected] " ,
"name" : "John Doe"
},
"session" : {
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzEwNTEyNDAwLCJzdWIiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6e30sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.abcdefghijklmnopqrstuvwxyz123456789" ,
"token_type" : "bearer" ,
"expires_in" : 3600 ,
"expires_at" : 1710512400 ,
"refresh_token" : "v1.Mr0hBPHnhKMpTjEXWKgBp2g3zXpLqr5u" ,
"user" : {
"id" : "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ,
"aud" : "authenticated" ,
"email" : "[email protected] " ,
"email_confirmed_at" : "2024-03-15T14:00:00.000Z" ,
"created_at" : "2024-03-15T14:00:00.000Z"
}
}
}
The access_token expires in 3600 seconds (1 hour). Use the refresh_token to obtain a new access token when it expires.
Password Requirements
Supabase enforces the following password requirements by default:
Minimum length: 6 characters
We recommend using at least 12 characters with a mix of uppercase, lowercase, numbers, and symbols
Logging In
Login Endpoint
POST /api/auth/login
Content-Type : application/json
{
"email" : "[email protected] " ,
"password" : "SecurePassword123!"
}
curl -X POST https://api.vaniykempire.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected] ",
"password": "SecurePassword123!"
}'
Response
{
"message" : "Login successful" ,
"user" : {
"id" : "65f7b3c8e1234567890abcde" ,
"email" : "[email protected] " ,
"name" : "John Doe"
},
"session" : {
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "bearer" ,
"expires_in" : 3600 ,
"refresh_token" : "v1.Mr0hBPHxxx..." ,
"user" : {
"id" : "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ,
"email" : "[email protected] "
}
}
}
Making Authenticated Requests
Include the access token in the Authorization header using the Bearer scheme:
GET /api/auth/profile
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
curl https://api.vaniykempire.com/api/auth/profile \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Authentication Middleware Implementation
Here’s how the API validates tokens (from src/middleware/auth.js:4-31):
const authenticate = async ( req , res , next ) => {
try {
// Extract token from Authorization header
const token = req . headers . authorization ?. replace ( 'Bearer ' , '' );
if ( ! token ) {
return res . status ( 401 ). json ({ error: 'No token provided' });
}
// Validate token with Supabase
const { data : { user }, error } = await supabase . auth . getUser ( token );
if ( error || ! user ) {
return res . status ( 401 ). json ({ error: 'Invalid token' });
}
// Get full user data from MongoDB including role
const mongoUser = await User . findOne ({ supabaseId: user . id });
if ( ! mongoUser ) {
return res . status ( 404 ). json ({ error: 'User not found' });
}
// Attach user data to request
req . user = user ; // Supabase user object
req . mongoUser = mongoUser ; // MongoDB user object with role
next ();
} catch ( error ) {
res . status ( 401 ). json ({ error: 'Authentication failed' });
}
};
The middleware attaches both req.user (Supabase user) and req.mongoUser (MongoDB user with role) to authenticated requests.
Token Expiration and Refresh
Access tokens expire after 1 hour (3600 seconds). When a token expires, you’ll receive a 401 Unauthorized response:
{
"error" : "Invalid token"
}
Refreshing Tokens
To refresh an expired access token, you need to use Supabase’s client library directly:
JavaScript (Supabase JS)
Python (Supabase Py)
import { createClient } from '@supabase/supabase-js'
const supabase = createClient (
'YOUR_SUPABASE_URL' ,
'YOUR_SUPABASE_ANON_KEY'
)
// Refresh the session
const { data , error } = await supabase . auth . refreshSession ({
refresh_token: localStorage . getItem ( 'refresh_token' )
})
if ( data . session ) {
const newAccessToken = data . session . access_token
const newRefreshToken = data . session . refresh_token
localStorage . setItem ( 'access_token' , newAccessToken )
localStorage . setItem ( 'refresh_token' , newRefreshToken )
}
The Vaniyk Empire API does not currently expose a refresh endpoint. You must use the Supabase client library to refresh tokens, or implement a custom refresh endpoint.
Automatic Token Refresh
Implement automatic token refresh to provide a seamless user experience:
class APIClient {
constructor ( supabaseUrl , supabaseKey ) {
this . supabase = createClient ( supabaseUrl , supabaseKey );
this . baseUrl = 'https://api.vaniykempire.com' ;
}
async request ( endpoint , options = {}) {
// Get current session
const { data : { session }, error } = await this . supabase . auth . getSession ();
if ( error || ! session ) {
throw new Error ( 'Not authenticated' );
}
// Make request with current token
const response = await fetch ( ` ${ this . baseUrl }${ endpoint } ` , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ session . access_token } `
}
});
// If token expired, refresh and retry
if ( response . status === 401 ) {
const { data : refreshData } = await this . supabase . auth . refreshSession ();
if ( refreshData . session ) {
// Retry request with new token
return fetch ( ` ${ this . baseUrl }${ endpoint } ` , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ refreshData . session . access_token } `
}
});
}
}
return response ;
}
}
// Usage
const client = new APIClient (
'YOUR_SUPABASE_URL' ,
'YOUR_SUPABASE_ANON_KEY'
);
const response = await client . request ( '/api/auth/profile' );
const data = await response . json ();
Admin Authentication
The API supports admin users with elevated privileges for content management.
Admin Signup
Create an admin account using a secret key:
POST /api/auth/signup/admin
Content-Type : application/json
{
"email" : "[email protected] " ,
"password" : "AdminPass123!" ,
"name" : "Admin User" ,
"adminSecret" : "your-admin-secret-key"
}
curl -X POST https://api.vaniykempire.com/api/auth/signup/admin \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected] ",
"password": "AdminPass123!",
"name": "Admin User",
"adminSecret": "your-admin-secret-key"
}'
The adminSecret must match the ADMIN_SECRET_KEY environment variable configured on the server. Never expose this secret in client-side code.
Admin Login
Admins can use a dedicated login endpoint that verifies admin role:
POST /api/auth/admin/login
Content-Type : application/json
{
"email" : "[email protected] " ,
"password" : "AdminPass123!"
}
The response includes the user’s role:
{
"message" : "Admin login successful" ,
"user" : {
"id" : "65f7b3c8e1234567890abce1" ,
"email" : "[email protected] " ,
"name" : "Admin User" ,
"role" : "admin"
},
"session" : {
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "bearer" ,
"expires_in" : 3600
}
}
Admin Middleware
Admin-only endpoints use the requireAdmin middleware (from src/middleware/auth.js:33-49):
const requireAdmin = async ( req , res , next ) => {
try {
if ( ! req . mongoUser ) {
return res . status ( 401 ). json ({ error: 'Authentication required' });
}
if ( req . mongoUser . role !== 'admin' ) {
return res . status ( 403 ). json ({
error: 'Access denied. Admin privileges required.'
});
}
next ();
} catch ( error ) {
res . status ( 500 ). json ({ error: 'Authorization check failed' });
}
};
Admin endpoints require both authenticate and requireAdmin middleware:
// Example: Create content (admin only)
router . post ( '/' ,
authenticate , // Validates JWT token
requireAdmin , // Checks user.role === 'admin'
contentController . createContent
);
User Profile
Retrieve the authenticated user’s profile:
GET /api/auth/profile
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response
{
"user" : {
"_id" : "65f7b3c8e1234567890abcde" ,
"supabaseId" : "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ,
"email" : "[email protected] " ,
"name" : "John Doe" ,
"role" : "user" ,
"emailVerified" : true ,
"createdAt" : "2024-03-15T14:00:00.000Z" ,
"updatedAt" : "2024-03-15T14:00:00.000Z"
}
}
Password Reset
The API supports password reset via email:
Request Password Reset
POST /api/auth/password-reset/request
Content-Type : application/json
{
"email" : "[email protected] "
}
curl -X POST https://api.vaniykempire.com/api/auth/password-reset/request \
-H "Content-Type: application/json" \
-d '{"email": "[email protected] "}'
Response
{
"message" : "Password reset email sent"
}
The implementation (from src/controllers/authController.js:83-98):
const { email } = req . body ;
const { error } = await supabase . auth . resetPasswordForEmail ( email , {
redirectTo: ` ${ process . env . FRONTEND_URL } /reset-password`
});
if ( error ) {
return res . status ( 400 ). json ({ error: error . message });
}
res . json ({ message: 'Password reset email sent' });
Supabase sends a password reset email with a secure token. The user clicks the link, which redirects to your frontend with the token, and your frontend then calls the update password endpoint.
Update Password
After receiving the reset token, update the password:
POST /api/auth/password-reset/update
Content-Type : application/json
{
"password" : "NewSecurePassword123!"
}
This endpoint requires the user to be authenticated with the reset token from the email. The Supabase client library handles this automatically when the user follows the reset link.
Email Verification
Resend Verification Email
POST /api/auth/email/resend
Content-Type : application/json
{
"email" : "[email protected] "
}
Verify Email
After the user clicks the verification link in their email:
POST /api/auth/email/verify
Content-Type : application/json
{
"token_hash" : "abc123..." ,
"type" : "email"
}
Security Best Practices
Store Tokens Securely Never store tokens in localStorage for sensitive applications. Use secure, httpOnly cookies or sessionStorage instead.
Use HTTPS Always use HTTPS in production to prevent token interception. The API rejects HTTP requests.
Implement Token Refresh Automatically refresh tokens before they expire to provide seamless user experience.
Handle 401 Errors Implement proper error handling for authentication failures and redirect users to login.
Common Authentication Errors
The request is missing the Authorization header. Include the header with format: Authorization: Bearer <access_token>
The token has expired or is malformed. Login again or refresh the token using Supabase client.
403 - Access denied. Admin privileges required
The endpoint requires admin role but the authenticated user is a regular user. Only admins can access content management endpoints.
The Supabase user exists but there’s no corresponding MongoDB user record. This shouldn’t happen in normal operation - contact support if you encounter this error.
Complete Authentication Example
Here’s a complete authentication implementation with automatic token refresh:
import { createClient } from '@supabase/supabase-js'
class VaniykAPIClient {
constructor ( apiBaseUrl , supabaseUrl , supabaseKey ) {
this . apiBaseUrl = apiBaseUrl ;
this . supabase = createClient ( supabaseUrl , supabaseKey );
}
// Sign up new user
async signup ( email , password , name ) {
const response = await fetch ( ` ${ this . apiBaseUrl } /api/auth/signup` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email , password , name })
});
return await response . json ();
}
// Login existing user
async login ( email , password ) {
const response = await fetch ( ` ${ this . apiBaseUrl } /api/auth/login` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email , password })
});
return await response . json ();
}
// Make authenticated request with automatic token refresh
async authenticatedRequest ( endpoint , options = {}) {
const { data : { session }, error } = await this . supabase . auth . getSession ();
if ( error || ! session ) {
throw new Error ( 'Not authenticated' );
}
const response = await fetch ( ` ${ this . apiBaseUrl }${ endpoint } ` , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ session . access_token } `
}
});
// Auto-refresh on 401
if ( response . status === 401 ) {
const { data : refreshData } = await this . supabase . auth . refreshSession ();
if ( refreshData . session ) {
return fetch ( ` ${ this . apiBaseUrl }${ endpoint } ` , {
... options ,
headers: {
... options . headers ,
'Authorization' : `Bearer ${ refreshData . session . access_token } `
}
});
}
}
return response ;
}
// Get current user profile
async getProfile () {
const response = await this . authenticatedRequest ( '/api/auth/profile' );
return await response . json ();
}
// Logout
async logout () {
await this . supabase . auth . signOut ();
}
}
// Usage
const client = new VaniykAPIClient (
'https://api.vaniykempire.com' ,
'YOUR_SUPABASE_URL' ,
'YOUR_SUPABASE_ANON_KEY'
);
// Sign up
await client . signup ( '[email protected] ' , 'SecurePass123!' , 'John Doe' );
// Login
await client . login ( '[email protected] ' , 'SecurePass123!' );
// Make authenticated requests
const profile = await client . getProfile ();
console . log ( profile );
// Logout
await client . logout ();
Next Steps
Quickstart Follow the quickstart guide to make your first authenticated request
API Reference Explore all available endpoints and see which require authentication