Overview
Masar Eagle uses OAuth 2.0 with OpenIddict for authentication and authorization. The API supports multiple authentication flows:
OTP Flow - Custom grant type for drivers and passengers using phone number verification
Password Flow - Username/password authentication for admins and companies
Refresh Token Flow - Token renewal without re-authentication
Token Endpoint
All authentication requests are made to the token endpoint:
This endpoint accepts form-encoded requests and returns JWT access tokens.
Authentication Flows
OTP Flow (Phone Number Verification)
Used for drivers and passengers to authenticate using phone number and OTP code.
Step 1: Request OTP
First, request an OTP code to be sent to the user’s phone:
curl -X POST https://api.masareagle.com/api/auth/send-otp \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "+966501234567"
}'
Response:
{
"message" : "تم إرسال رمز التحقق بنجاح" ,
"expiresAt" : "2024-03-10T14:35:00.000Z" ,
"code" : "123456"
}
The code field is only included in development/testing environments. In production, the OTP is sent via SMS only.
OTP Configuration
Code Length : 6 digits
Expiry Time : 5 minutes
Max Attempts : 5 verification attempts
Resend Limit : 3 times within 30-minute window
Cooldown : 1 minute between resend requests
Step 2: Resend OTP (Optional)
If the user didn’t receive the OTP, they can request a resend:
curl -X POST https://api.masareagle.com/api/auth/resend-otp \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "+966501234567"
}'
Step 3: Exchange OTP for Token
Once the user enters the OTP code, exchange it for an access token:
curl -X POST https://api.masareagle.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:masareagle:otp" \
-d "phone_number=%2B966501234567" \
-d "otp_code=123456" \
-d "user_type=driver"
Request Parameters:
Must be urn:masareagle:otp for OTP authentication
Phone number in E.164 format (e.g., +966501234567)
6-digit OTP code received via SMS
User type: driver or passenger
Success Response:
{
"access_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"refresh_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"scope" : "openid offline_access roles api"
}
Password Flow (Admin & Company)
Used for admins and companies to authenticate using email and password.
curl -X POST https://api.masareagle.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "[email protected] " \
-d "password=SecurePassword123!" \
-d "user_type=admin"
Request Parameters:
Must be password for credential-based authentication
Email address of the admin or company user
User type: admin or company
Password flow is only supported for admin and company user types. Attempting to use it with driver or passenger will return an error.
Refresh Token Flow
Use a refresh token to obtain a new access token without re-authenticating:
curl -X POST https://api.masareagle.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Request Parameters:
The refresh token received from a previous authentication
Token Details
Access Token
Format : JWT (JSON Web Token)
Lifetime : 60 minutes (configurable)
Algorithm : RS256 (RSA with SHA-256)
Issuer : masareagle.identity
Audience : masar-eagle-api
Token Claims
The access token includes the following claims:
{
"sub" : "123e4567-e89b-12d3-a456-426614174000" ,
"role" : "driver" ,
"iss" : "https://masareagle.identity" ,
"aud" : "masar-eagle-api" ,
"scope" : "openid offline_access roles api" ,
"exp" : 1710345000 ,
"iat" : 1710341400
}
User role: admin, company, driver, or passenger
Issuer - Identity service URL
Granted scopes (space-separated)
Expiration time (Unix timestamp)
Issued at time (Unix timestamp)
Refresh Token
Lifetime : 30 days
Usage : Can be used once to obtain a new access token and refresh token pair
Storage : Store securely on client (encrypted storage, not localStorage)
Using Access Tokens
Include the access token in the Authorization header of API requests:
curl -X GET https://api.masareagle.com/api/drivers/me \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Authentication Errors
Authentication failures return an HTTP 403 Forbidden response with error details:
Invalid OTP
Invalid Credentials
Missing Parameters
Unsupported Grant Type
Expired Refresh Token
{
"error" : "invalid_grant" ,
"error_description" : "رمز التحقق غير صالح أو منتهي الصلاحية"
}
Common Error Codes
Error Code Description invalid_requestRequired parameter missing or invalid invalid_grantInvalid credentials, OTP, or refresh token unsupported_grant_typeGrant type not supported invalid_clientClient authentication failed
User Provisioning
When a user authenticates for the first time:
The OTP is verified
A UserAuthenticatedEvent is published to the message bus
The Users service provisions the user profile asynchronously
The token endpoint waits up to 1 second for provisioning, then retries
An access token is issued with the user’s ID
If provisioning takes longer than expected, the token endpoint will retry once after a 1-second delay. This handles race conditions during initial user creation.
Security Best Practices
Token Storage
Store tokens securely (encrypted storage)
Never store in localStorage (XSS risk)
Use secure, httpOnly cookies or mobile secure storage
Token Refresh
Refresh tokens before expiry
Implement automatic token refresh
Handle refresh failures gracefully
HTTPS Only
Always use HTTPS in production
Never send tokens over HTTP
Validate SSL certificates
Error Handling
Don’t log tokens or sensitive data
Handle 401 responses by re-authenticating
Implement exponential backoff for retries
Code Example: Complete Authentication Flow
class AuthService {
constructor ( baseURL ) {
this . baseURL = baseURL ;
this . accessToken = null ;
this . refreshToken = null ;
}
async sendOTP ( phoneNumber ) {
const response = await fetch ( ` ${ this . baseURL } /api/auth/send-otp` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ phoneNumber })
});
if ( ! response . ok ) {
throw new Error ( 'Failed to send OTP' );
}
return response . json ();
}
async verifyOTP ( phoneNumber , otpCode , userType ) {
const params = new URLSearchParams ({
grant_type: 'urn:masareagle:otp' ,
phone_number: phoneNumber ,
otp_code: otpCode ,
user_type: userType
});
const response = await fetch ( ` ${ this . baseURL } /connect/token` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/x-www-form-urlencoded' },
body: params
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( error . error_description || 'Authentication failed' );
}
const tokens = await response . json ();
this . accessToken = tokens . access_token ;
this . refreshToken = tokens . refresh_token ;
// Store tokens securely
this . storeTokens ( tokens );
return tokens ;
}
async refreshAccessToken () {
if ( ! this . refreshToken ) {
throw new Error ( 'No refresh token available' );
}
const params = new URLSearchParams ({
grant_type: 'refresh_token' ,
refresh_token: this . refreshToken
});
const response = await fetch ( ` ${ this . baseURL } /connect/token` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/x-www-form-urlencoded' },
body: params
});
if ( ! response . ok ) {
// Refresh token invalid - need to re-authenticate
this . clearTokens ();
throw new Error ( 'Session expired' );
}
const tokens = await response . json ();
this . accessToken = tokens . access_token ;
this . refreshToken = tokens . refresh_token ;
this . storeTokens ( tokens );
return tokens ;
}
async apiRequest ( endpoint , options = {}) {
const headers = {
... options . headers ,
'Authorization' : `Bearer ${ this . accessToken } `
};
let response = await fetch ( ` ${ this . baseURL }${ endpoint } ` , {
... options ,
headers
});
// Auto-refresh on 401
if ( response . status === 401 ) {
await this . refreshAccessToken ();
headers . Authorization = `Bearer ${ this . accessToken } ` ;
response = await fetch ( ` ${ this . baseURL }${ endpoint } ` , {
... options ,
headers
});
}
return response ;
}
storeTokens ( tokens ) {
// Store in secure storage (implementation depends on platform)
// For web: encrypted sessionStorage or secure cookie
// For mobile: Keychain/Keystore
}
clearTokens () {
this . accessToken = null ;
this . refreshToken = null ;
// Clear from secure storage
}
}
// Usage
const auth = new AuthService ( 'https://api.masareagle.com' );
// Step 1: Send OTP
await auth . sendOTP ( '+966501234567' );
// Step 2: User enters OTP code
await auth . verifyOTP ( '+966501234567' , '123456' , 'driver' );
// Step 3: Make authenticated requests
const response = await auth . apiRequest ( '/api/drivers/me' );
const profile = await response . json ();
Next Steps
Error Handling Learn how to handle API errors
API Overview Explore the full API structure