Overview
The OAuth integration system allows users to authenticate and link their accounts with third-party providers like Google, Telegram, and others. It supports both registration and login flows, as well as account linking for existing users.
Supported Providers
Currently supported OAuth providers:
Google : OAuth 2.0
Telegram : Telegram Bot authentication
More providers can be configured
OAuth Flow
Standard OAuth 2.0 Flow
Get Authorization URL
Get the OAuth authorization URL to redirect the user to the provider’s login page.
Endpoint: GET /api/oauth/:provider/authorize
Parameters:
provider: URL path parameter - provider name (e.g., google, telegram)
redirect_uri: Query parameter - Optional, custom redirect URI after authentication
state: Query parameter - Optional, state parameter for CSRF protection
Example Request:
GET /api/oauth/google/authorize?redirect_uri=https://app.rcoinx.com/callback&state=random_state_string
Response: 200 OK
{
"code" : 200 ,
"message" : "Authorization URL generated successfully" ,
"data" : {
"authorization_url" : "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&scope=...&state=..." ,
"state" : "random_state_string" ,
"session_id" : "oauth_session_abc123"
}
}
Example:
JavaScript
TypeScript
Flutter
async function getOAuthAuthorizationURL ( provider , redirectUri , state ) {
const params = new URLSearchParams ();
if ( redirectUri ) params . append ( 'redirect_uri' , redirectUri );
if ( state ) params . append ( 'state' , state );
const url = `https://api.rcoinx.com/api/oauth/ ${ provider } /authorize? ${ params } ` ;
const response = await fetch ( url , {
method: 'GET' ,
headers: {
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data . data ;
}
// Usage - Redirect user to OAuth provider
async function loginWithGoogle () {
try {
const state = generateRandomState (); // Generate CSRF token
const result = await getOAuthAuthorizationURL (
'google' ,
'https://app.rcoinx.com/oauth/callback' ,
state
);
// Store state for verification
sessionStorage . setItem ( 'oauth_state' , state );
sessionStorage . setItem ( 'oauth_session_id' , result . session_id );
// Redirect user to OAuth provider
window . location . href = result . authorization_url ;
} catch ( error ) {
console . error ( 'Failed to initiate OAuth:' , error );
}
}
function generateRandomState () {
return Math . random (). toString ( 36 ). substring ( 2 , 15 ) +
Math . random (). toString ( 36 ). substring ( 2 , 15 );
}
OAuth Callback
Handle the OAuth callback after the user authorizes the application with the provider.
Endpoint: GET /api/oauth/:provider/callback
Query Parameters:
code: Authorization code from the provider
state: State parameter for CSRF verification
session_id: OAuth session ID (optional, from authorization step)
Example Request:
GET /api/oauth/google/callback?code=4/0AY0e-g7xQz...&state=random_state_string
Response: 200 OK
{
"code" : 200 ,
"message" : "OAuth authentication 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@gmail.com" ,
"verified" : true
}
},
"is_new_user" : false ,
"provider" : "google"
}
}
If this is the user’s first time logging in with OAuth, a new account will be created automatically. The is_new_user field indicates whether this is a new registration.
Example:
// This is typically handled by your OAuth callback route
// Example: /oauth/callback route in your frontend
async function handleOAuthCallback () {
const urlParams = new URLSearchParams ( window . location . search );
const code = urlParams . get ( 'code' );
const state = urlParams . get ( 'state' );
const provider = urlParams . get ( 'provider' ) || 'google' ;
// Verify state (CSRF protection)
const savedState = sessionStorage . getItem ( 'oauth_state' );
if ( state !== savedState ) {
console . error ( 'State mismatch - possible CSRF attack' );
window . location . href = '/login?error=security' ;
return ;
}
try {
// The callback is typically handled server-side
// If you need to call it from frontend:
const response = await fetch (
`https://api.rcoinx.com/api/oauth/ ${ provider } /callback?code= ${ code } &state= ${ state } ` ,
{
method: 'GET' ,
headers: {
'Content-Type' : 'application/json'
}
}
);
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
// 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 ));
// Clean up
sessionStorage . removeItem ( 'oauth_state' );
sessionStorage . removeItem ( 'oauth_session_id' );
// Show welcome message for new users
if ( data . data . is_new_user ) {
console . log ( 'Welcome! Your account has been created.' );
}
// Redirect to dashboard
window . location . href = '/dashboard' ;
} catch ( error ) {
console . error ( 'OAuth callback failed:' , error );
window . location . href = '/login?error=oauth_failed' ;
}
}
Validate OAuth Session
Validate an OAuth session before completing the authentication.
Endpoint: POST /api/oauth/:provider/validate-session
Request:
{
"session_id" : "oauth_session_abc123"
}
Response: 200 OK
{
"code" : 200 ,
"message" : "Session is valid" ,
"data" : {
"valid" : true ,
"expires_at" : "2025-10-13T12:35:00Z" ,
"provider" : "google"
}
}
Example:
async function validateOAuthSession ( provider , sessionId ) {
const response = await fetch (
`https://api.rcoinx.com/api/oauth/ ${ provider } /validate-session` ,
{
method: 'POST' ,
headers: {
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ session_id: sessionId })
}
);
const data = await response . json ();
return data . data ;
}
Get User OAuth Connections
Get all OAuth connections for the authenticated user.
Endpoint: GET /api/oauth/connections
Headers:
Authorization : Bearer <access_token>
Response: 200 OK
{
"code" : 200 ,
"message" : "OAuth connections retrieved successfully" ,
"data" : [
{
"id" : 1 ,
"provider" : "google" ,
"provider_user_id" : "1234567890" ,
"email" : "john@gmail.com" ,
"connected_at" : "2025-10-01T10:00:00Z" ,
"last_used_at" : "2025-10-13T12:00:00Z"
},
{
"id" : 2 ,
"provider" : "telegram" ,
"provider_user_id" : "9876543210" ,
"username" : "johndoe_tg" ,
"connected_at" : "2025-10-05T15:00:00Z" ,
"last_used_at" : "2025-10-12T18:00:00Z"
}
]
}
Example:
async function getOAuthConnections () {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/oauth/connections' , {
method: 'GET' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data . data ;
}
Disconnect OAuth Account
Disconnect an OAuth provider from the user’s account.
Endpoint: POST /api/oauth/:provider/disconnect
Headers:
Authorization : Bearer <access_token>
Request:
{
"provider_user_id" : "1234567890"
}
Response: 200 OK
{
"code" : 200 ,
"message" : "OAuth account disconnected successfully" ,
"data" : {
"success" : true ,
"provider" : "google"
}
}
Ensure the user has another authentication method (password, phone, or another OAuth provider) before disconnecting. Otherwise, they may lose access to their account.
Example:
async function disconnectOAuthAccount ( provider , providerUserId ) {
const accessToken = localStorage . getItem ( 'access_token' );
const confirmed = confirm (
`Are you sure you want to disconnect your ${ provider } account?`
);
if ( ! confirmed ) return ;
const response = await fetch (
`https://api.rcoinx.com/api/oauth/ ${ provider } /disconnect` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ provider_user_id: providerUserId })
}
);
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data . data ;
}
Complete OAuth Integration Example
Here’s a complete example with UI:
import React , { useState , useEffect } from 'react' ;
function OAuthManager () {
const [ connections , setConnections ] = useState ([]);
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
loadConnections ();
}, []);
const loadConnections = async () => {
try {
const data = await getOAuthConnections ();
setConnections ( data );
} catch ( error ) {
console . error ( 'Failed to load connections:' , error );
} finally {
setLoading ( false );
}
};
const handleConnect = async ( provider ) => {
try {
const state = generateRandomState ();
const redirectUri = ` ${ window . location . origin } /oauth/callback` ;
const result = await getOAuthAuthorizationURL ( provider , redirectUri , state );
sessionStorage . setItem ( 'oauth_state' , state );
sessionStorage . setItem ( 'oauth_session_id' , result . session_id );
sessionStorage . setItem ( 'oauth_action' , 'connect' ); // Indicate this is for linking
window . location . href = result . authorization_url ;
} catch ( error ) {
alert ( 'Failed to connect: ' + error . message );
}
};
const handleDisconnect = async ( provider , providerUserId ) => {
// Check if user has other auth methods
if ( connections . length === 1 && ! hasPassword ()) {
alert ( 'You must have at least one authentication method. Please set a password first.' );
return ;
}
try {
await disconnectOAuthAccount ( provider , providerUserId );
await loadConnections ();
alert ( 'Account disconnected successfully' );
} catch ( error ) {
alert ( 'Failed to disconnect: ' + error . message );
}
};
const hasPassword = () => {
// Check if user has password set (implement based on your API)
return true ; // Placeholder
};
const getProviderIcon = ( provider ) => {
const icons = {
google: '🔵' ,
telegram: '✈️' ,
facebook: '📘' ,
twitter: '🐦'
};
return icons [ provider ] || '🔗' ;
};
if ( loading ) return < div > Loading... </ div > ;
return (
< div className = "oauth-manager" >
< h2 > Connected Accounts </ h2 >
< div className = "connected-accounts" >
{ connections . length === 0 ? (
< p > No accounts connected </ p >
) : (
connections . map ( conn => (
< div key = { conn . id } className = "connection-card" >
< div className = "provider-info" >
< span className = "icon" > { getProviderIcon ( conn . provider ) } </ span >
< div >
< strong > { conn . provider } </ strong >
< p > { conn . email || conn . username } </ p >
< small > Connected: {new Date ( conn . connected_at ). toLocaleDateString () } </ small >
</ div >
</ div >
< button
onClick = { () => handleDisconnect ( conn . provider , conn . provider_user_id ) }
className = "disconnect-btn"
>
Disconnect
</ button >
</ div >
))
) }
</ div >
< div className = "connect-new" >
< h3 > Connect New Account </ h3 >
< div className = "provider-buttons" >
{ [ 'google' , 'telegram' ]. map ( provider => {
const isConnected = connections . some ( c => c . provider === provider );
return (
< button
key = { provider }
onClick = { () => handleConnect ( provider ) }
disabled = { isConnected }
className = "provider-btn"
>
{ getProviderIcon ( provider ) } { provider }
{ isConnected && ' (Connected)' }
</ button >
);
}) }
</ div >
</ div >
</ div >
);
}
export default OAuthManager ;
Telegram-Specific Flow
Telegram uses a slightly different OAuth flow. See the Telegram OAuth documentation for specific implementation details.
Security Best Practices
1. Verify State Parameter
Always verify the state parameter to prevent CSRF attacks:
function verifyState ( receivedState ) {
const savedState = sessionStorage . getItem ( 'oauth_state' );
if ( ! savedState || receivedState !== savedState ) {
throw new Error ( 'Invalid state parameter - possible CSRF attack' );
}
sessionStorage . removeItem ( 'oauth_state' );
return true ;
}
2. Use HTTPS
OAuth requires HTTPS in production. Ensure your redirect URIs use HTTPS:
function getRedirectUri () {
if ( process . env . NODE_ENV === 'production' ) {
return 'https://app.rcoinx.com/oauth/callback' ;
}
return 'http://localhost:3000/oauth/callback' ;
}
3. Handle Popup Blockers
For popup-based OAuth flows:
function openOAuthPopup ( url , provider ) {
const width = 600 ;
const height = 700 ;
const left = window . screen . width / 2 - width / 2 ;
const top = window . screen . height / 2 - height / 2 ;
const popup = window . open (
url ,
` ${ provider } _oauth` ,
`width= ${ width } ,height= ${ height } ,left= ${ left } ,top= ${ top } `
);
if ( ! popup || popup . closed || typeof popup . closed === 'undefined' ) {
alert ( 'Please enable popups for this site to use OAuth login' );
return null ;
}
return popup ;
}
4. Store Minimal Data
Only store necessary OAuth data:
// Good - Store only what's needed
localStorage . setItem ( 'access_token' , token );
// Bad - Don't store OAuth provider tokens
// localStorage.setItem('google_access_token', googleToken);
Error Handling
Common Errors
Error Description Solution invalid_stateState parameter mismatch Clear session and restart OAuth flow invalid_codeAuthorization code is invalid or expired Restart OAuth flow provider_errorError from OAuth provider Check provider status, retry later account_existsEmail already registered Link account or use different email
Error Handling Example
async function handleOAuthError ( error ) {
const errorMessages = {
invalid_state: 'Security verification failed. Please try again.' ,
invalid_code: 'Authentication expired. Please try again.' ,
provider_error: 'The authentication provider is unavailable. Please try again later.' ,
account_exists: 'An account with this email already exists. Please login instead.'
};
const message = errorMessages [ error . code ] || error . message || 'An error occurred' ;
alert ( message );
// Redirect to login
window . location . href = '/login' ;
}