Overview
The Authentication API provides secure user registration, login, and token management functionality. It supports both email and phone number-based authentication with JWT token issuance.
Registration
Standard Registration
Register a new user with email and phone number.
Endpoint: POST /api/register
Request:
{
"username" : "johndoe" ,
"email" : "john@example.com" ,
"phone" : "1234567890" ,
"country_code" : "1" ,
"password" : "SecurePass123!" ,
"password_confirmation" : "SecurePass123!" ,
"meta_data" : "{ \" referral_code \" : \" ABC123 \" }"
}
Validation Rules:
username: Required, 3-50 characters, must be unique
email: Required, valid email format, must be unique
phone: Required, minimum 10 digits, numeric only
country_code: Required, 1-3 digits
password: Required, minimum 8 characters
password_confirmation: Required, must match password
meta_data: Optional, valid JSON string
Response: 200 OK
{
"code" : 200 ,
"message" : "Registration successful" ,
"data" : {
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"refresh_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"access_token_expires_at" : "2025-10-13T12:30:00Z" ,
"refresh_token_expires_at" : "2025-10-20T12:00:00Z" ,
"user" : {
"id" : 123 ,
"username" : "johndoe" ,
"email" : {
"email" : "john@example.com" ,
"verified" : false
},
"phone" : {
"phone" : "1234567890" ,
"country_code" : "1" ,
"verified" : false
},
"kyc_status" : "pending" ,
"is_active" : true ,
"is_frozen" : false
}
}
}
Example:
JavaScript
TypeScript
Flutter
async function register ( userData ) {
const response = await fetch ( 'https://api.rcoinx.com/api/register' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json'
},
body: JSON . stringify ( userData )
});
const data = await response . json ();
if ( response . ok ) {
// Store tokens
localStorage . setItem ( 'access_token' , data . data . access_token );
localStorage . setItem ( 'refresh_token' , data . data . refresh_token );
// Redirect to dashboard
window . location . href = '/dashboard' ;
} else {
throw new Error ( data . message );
}
return data ;
}
// Usage
try {
await register ({
username: 'johndoe' ,
email: 'john@example.com' ,
phone: '1234567890' ,
country_code: '1' ,
password: 'SecurePass123!' ,
password_confirmation: 'SecurePass123!'
});
} catch ( error ) {
console . error ( 'Registration failed:' , error );
}
Multi-Step Registration
For a more controlled registration flow with SMS OTP verification, see Session Management Registration .
Login
Login with Email
Endpoint: POST /api/login
Request:
{
"email" : "john@example.com" ,
"password" : "SecurePass123!" ,
"remember" : false
}
Parameters:
email: Required, valid email address
password: Required, minimum 8 characters
remember: Optional boolean, extends refresh token lifetime
Response: 200 OK
{
"code" : 200 ,
"message" : "Login successful" ,
"data" : {
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"refresh_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"access_token_expires_at" : "2025-10-13T12:30:00Z" ,
"refresh_token_expires_at" : "2025-10-20T12:00:00Z" ,
"user" : {
"id" : 123 ,
"username" : "johndoe" ,
"email" : {
"email" : "john@example.com" ,
"verified" : true
},
"phone" : {
"phone" : "1234567890" ,
"country_code" : "1" ,
"verified" : true
},
"kyc_status" : "approved" ,
"is_active" : true ,
"is_frozen" : false
}
}
}
Login with Phone
Endpoint: POST /api/login
Request:
{
"phone" : "1234567890" ,
"country_code" : "1" ,
"password" : "SecurePass123!" ,
"remember" : true
}
Parameters:
phone: Required, minimum 10 digits
country_code: Required, 1-3 digits
password: Required, minimum 8 characters
remember: Optional boolean
Example:
async function login ( email , password , remember = false ) {
const response = await fetch ( 'https://api.rcoinx.com/api/login' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ email , password , remember })
});
const data = await response . json ();
if ( response . ok ) {
// Store tokens
localStorage . setItem ( 'access_token' , data . data . access_token );
localStorage . setItem ( 'refresh_token' , data . data . refresh_token );
localStorage . setItem ( 'user' , JSON . stringify ( data . data . user ));
return data ;
} else {
throw new Error ( data . message );
}
}
// Usage
try {
const result = await login ( 'john@example.com' , 'SecurePass123!' , true );
console . log ( 'Login successful:' , result );
} catch ( error ) {
console . error ( 'Login failed:' , error );
}
Login Flow
Token Refresh
When the access token expires, use the refresh token to obtain a new access token without requiring the user to log in again.
Endpoint: POST /api/refresh
Headers:
Authorization : Bearer <refresh_token>
Response: 200 OK
{
"code" : 200 ,
"message" : "Token refreshed successfully" ,
"data" : {
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"access_token_expires_at" : "2025-10-13T12:45:00Z"
}
}
Example:
async function refreshAccessToken () {
const refreshToken = localStorage . getItem ( 'refresh_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/refresh' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ refreshToken } ` ,
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
if ( response . ok ) {
// Update access token
localStorage . setItem ( 'access_token' , data . data . access_token );
return data . data . access_token ;
} else {
// Refresh token expired, redirect to login
localStorage . clear ();
window . location . href = '/login' ;
throw new Error ( 'Session expired' );
}
}
// Automatic token refresh with axios interceptor
axios . interceptors . response . use (
response => response ,
async error => {
const originalRequest = error . config ;
if ( error . response . status === 401 && ! originalRequest . _retry ) {
originalRequest . _retry = true ;
try {
const newToken = await refreshAccessToken ();
originalRequest . headers [ 'Authorization' ] = `Bearer ${ newToken } ` ;
return axios ( originalRequest );
} catch ( refreshError ) {
return Promise . reject ( refreshError );
}
}
return Promise . reject ( error );
}
);
Logout
Endpoint: POST /api/logout
Headers:
Authorization : Bearer <access_token>
Response: 200 OK
{
"code" : 200 ,
"message" : "Logout successful"
}
Example:
async function logout () {
const accessToken = localStorage . getItem ( 'access_token' );
try {
await fetch ( 'https://api.rcoinx.com/api/logout' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
}
});
} catch ( error ) {
console . error ( 'Logout error:' , error );
} finally {
// Clear local storage regardless of API response
localStorage . clear ();
window . location . href = '/login' ;
}
}
The current logout implementation is a client-side operation. The API returns success but doesn’t invalidate the token server-side. To fully logout and revoke sessions, use the Session Logout endpoints .
Password Change
Change the authenticated user’s password.
Endpoint: PUT /api/password
Headers:
Authorization : Bearer <access_token>
Request:
{
"old_password" : "OldPassword123!" ,
"new_password" : "NewPassword456!" ,
"new_password_confirmation" : "NewPassword456!"
}
Response: 200 OK
{
"code" : 200 ,
"message" : "Password changed successfully"
}
Example:
async function changePassword ( oldPassword , newPassword ) {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/password' , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
old_password: oldPassword ,
new_password: newPassword ,
new_password_confirmation: newPassword
})
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data ;
}
Forget Password Flow
For password recovery, see Session Management - Forget Password .
Error Responses
Invalid Credentials
Status: 401 Unauthorized
{
"code" : 401 ,
"message" : "Invalid credentials" ,
"error" : "Email or password is incorrect"
}
User Already Exists
Status: 409 Conflict
{
"code" : 409 ,
"message" : "User already exists" ,
"error" : "Email address is already registered"
}
Validation Error
Status: 422 Unprocessable Entity
{
"code" : 422 ,
"message" : "Validation failed" ,
"error" : "password: must be at least 8 characters"
}
Account Frozen
Status: 403 Forbidden
{
"code" : 403 ,
"message" : "Account is frozen" ,
"error" : "Your account has been frozen. Please contact support."
}
Best Practices
1. Secure Token Storage
Never store tokens in localStorage if your application is vulnerable to XSS attacks. Consider using httpOnly cookies for sensitive applications.
// Good: Store in httpOnly cookie (backend sets)
// The backend should set tokens in httpOnly cookies
// Acceptable: Store in localStorage with proper XSS protection
if ( typeof window !== 'undefined' ) {
localStorage . setItem ( 'access_token' , token );
}
// Bad: Store in regular cookies or global variables
document . cookie = `token= ${ token } ` ; // Vulnerable to XSS
window . token = token ; // Accessible to any script
2. Automatic Token Refresh
Implement automatic token refresh before expiration:
const TOKEN_REFRESH_BUFFER = 60000 ; // 1 minute before expiration
function scheduleTokenRefresh ( expiresAt ) {
const expiryTime = new Date ( expiresAt ). getTime ();
const now = Date . now ();
const timeUntilRefresh = expiryTime - now - TOKEN_REFRESH_BUFFER ;
if ( timeUntilRefresh > 0 ) {
setTimeout ( async () => {
try {
await refreshAccessToken ();
} catch ( error ) {
console . error ( 'Auto refresh failed:' , error );
}
}, timeUntilRefresh );
}
}
// After login
scheduleTokenRefresh ( data . data . access_token_expires_at );
3. Handle Network Errors
async function makeAuthRequest ( url , options ) {
try {
const response = await fetch ( url , options );
return await response . json ();
} catch ( error ) {
if ( error . name === 'TypeError' ) {
// Network error
throw new Error ( 'Network error. Please check your connection.' );
}
throw error ;
}
}
4. Implement Loading States
function LoginForm () {
const [ loading , setLoading ] = useState ( false );
const [ error , setError ] = useState ( null );
const handleLogin = async ( email , password ) => {
setLoading ( true );
setError ( null );
try {
await login ( email , password );
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
};
return (
< form onSubmit = { handleLogin } >
{ error && < div className = "error" > { error } </ div > }
< button disabled = { loading } >
{ loading ? 'Logging in...' : 'Login' }
</ button >
</ form >
);
}