The Authorization Code flow uses a client_secret to authenticate when exchanging the authorization code for tokens. Use this flow when your application runs on a server and can securely store credentials — for example, Node.js, Python, or PHP backends.
Prerequisites
Before you implement this flow, ensure you have:
OAuth2 client credentials — Register your app in Client Center / Manage Account to get client_id and client_secret
Redirect URI — Must be pre-registered in Client Center / Manage Account and must exactly match (protocol, host, path, trailing slashes)
Server-side runtime — Node.js 18+, Python 3.8+, or equivalent for your language
Session or secure storage — To persist state between redirect and callback, and to store tokens after exchange
The client_secret is shown only once when you create the client. Copy it immediately and store it in environment variables or a secrets manager. If lost, regenerate it in Client Center / Manage Account.
Step 1: Build the authorization URL and redirect
Redirect the user to the Aries authorization endpoint. They will see a login and consent screen where they can approve or deny the requested permissions.
Endpoint: https://app.aries.com/oauth2/authorize
Required query parameters:
Parameter Description response_typeMust be code client_idYour OAuth2 client ID redirect_uriMust exactly match a registered URI scopeSpace-separated scopes (e.g., account:information order:execution) stateCryptographically random string for CSRF protection — verify on callback
const crypto = require ( 'crypto' );
// Generate state and save to session for callback verification
const state = crypto . randomBytes ( 32 ). toString ( 'hex' );
req . session . oauth_state = state ;
const params = new URLSearchParams ({
response_type: 'code' ,
client_id: process . env . ARIES_CLIENT_ID ,
redirect_uri: process . env . ARIES_REDIRECT_URI ,
scope: 'account:information order:execution market:information' ,
state: state ,
});
const authUrl = `https://app.aries.com/oauth2/authorize? ${ params } ` ;
res . redirect ( authUrl );
import secrets
from urllib.parse import urlencode
# Generate state and save to session for callback verification
state = secrets.token_hex( 32 )
request.session[ 'oauth_state' ] = state
params = urlencode({
'response_type' : 'code' ,
'client_id' : os.environ[ 'ARIES_CLIENT_ID' ],
'redirect_uri' : os.environ[ 'ARIES_REDIRECT_URI' ],
'scope' : 'account:information order:execution market:information' ,
'state' : state,
})
auth_url = f 'https://app.aries.com/oauth2/authorize? { params } '
return redirect(auth_url)
Always generate a new, cryptographically random state for each authorization request and verify it when the user returns. This prevents CSRF attacks.
Step 2: Handle the callback
After the user approves, Aries redirects to your redirect_uri with an authorization code and the state you provided. If the user denies access, you receive error parameters instead.
Success callback:
https://yourapp.com/oauth/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
Error callback (user denied or error):
https://yourapp.com/oauth/callback?error=access_denied&error_description=User+denied+access&state=YOUR_STATE
Parameter Description codeAuthorization code — valid for 10 minutes, single-use. Exchange immediately. stateMust match the value you sent. Verify before proceeding.
app . get ( '/oauth/callback' , async ( req , res ) => {
// Verify state to prevent CSRF
if ( req . query . state !== req . session . oauth_state ) {
return res . status ( 400 ). send ( 'Invalid state parameter' );
}
// Handle user denial or other errors
if ( req . query . error ) {
return res . status ( 400 ). send ( `Authorization error: ${ req . query . error } ` );
}
const authCode = req . query . code ;
if ( ! authCode ) {
return res . status ( 400 ). send ( 'No authorization code received' );
}
try {
const tokens = await exchangeCodeForToken ( authCode );
req . session . access_token = tokens . access_token ;
req . session . refresh_token = tokens . refresh_token ;
req . session . token_expires_at = Date . now () + tokens . expires_in * 1000 ;
res . redirect ( '/dashboard' );
} catch ( err ) {
res . status ( 500 ). send ( 'Token exchange failed' );
}
});
@app.route ( '/oauth/callback' )
def oauth_callback ():
# Verify state to prevent CSRF
if request.args.get( 'state' ) != session.get( 'oauth_state' ):
return 'Invalid state parameter' , 400
# Handle user denial or other errors
if request.args.get( 'error' ):
return f "Authorization error: { request.args.get( 'error' ) } " , 400
auth_code = request.args.get( 'code' )
if not auth_code:
return 'No authorization code received' , 400
try :
tokens = exchange_code_for_token(auth_code)
session[ 'access_token' ] = tokens[ 'access_token' ]
session[ 'refresh_token' ] = tokens[ 'refresh_token' ]
session[ 'token_expires_at' ] = time.time() + tokens[ 'expires_in' ]
return redirect( '/dashboard' )
except Exception :
return 'Token exchange failed' , 500
Step 3: Exchange the code for tokens
Send the authorization code to the token endpoint to receive an access token and refresh token. The code is single-use — exchange it immediately after receiving it.
Endpoint: POST https://api.aries.com/v1/oauth2/token
Request body:
Field Required Description client_idYes Your OAuth2 client ID client_secretYes Your OAuth2 client secret grant_typeYes codecodeYes The authorization code from the callback redirect_uriYes Must match the redirect URI used in Step 1
curl -X POST 'https://api.aries.com/v1/oauth2/token' \
-H 'Content-Type: application/json' \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "code",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "YOUR_REDIRECT_URI"
}'
async function exchangeCodeForToken ( authCode ) {
const response = await fetch ( 'https://api.aries.com/v1/oauth2/token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
client_id: process . env . ARIES_CLIENT_ID ,
client_secret: process . env . ARIES_CLIENT_SECRET ,
grant_type: 'code' ,
code: authCode ,
redirect_uri: process . env . ARIES_REDIRECT_URI ,
}),
});
if ( ! response . ok ) {
const err = await response . json ();
throw new Error ( err . error_description || `Token exchange failed: ${ response . status } ` );
}
return await response . json ();
}
def exchange_code_for_token ( auth_code ):
response = requests.post(
'https://api.aries.com/v1/oauth2/token' ,
json = {
'client_id' : os.environ[ 'ARIES_CLIENT_ID' ],
'client_secret' : os.environ[ 'ARIES_CLIENT_SECRET' ],
'grant_type' : 'code' ,
'code' : auth_code,
'redirect_uri' : os.environ[ 'ARIES_REDIRECT_URI' ],
}
)
response.raise_for_status()
return response.json()
Response:
{
"access_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"refresh_token" : "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ..." ,
"scope" : "account:information order:execution market:information"
}
Store the access_token, refresh_token, and expires_in (use it to compute expiry time). Replace your stored refresh_token if a new one is returned.
Step 4: Make authenticated API requests
Include the access token in the Authorization header for every API request.
Authorization: Bearer YOUR_ACCESS_TOKEN
curl -X GET 'https://api.aries.com/v1/users/me/accounts' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
async function callApi ( endpoint , accessToken ) {
const response = await fetch ( `https://api.aries.com ${ endpoint } ` , {
headers: { 'Authorization' : `Bearer ${ accessToken } ` },
signal: AbortSignal . timeout ( 10000 ),
});
if ( response . status === 401 ) {
throw new Error ( 'Token expired or invalid — refresh or re-authenticate' );
}
if ( response . status === 403 ) {
throw new Error ( 'Insufficient permissions — check granted scopes' );
}
if ( ! response . ok ) {
throw new Error ( `HTTP ${ response . status } ` );
}
return await response . json ();
}
const accounts = await callApi ( '/v1/users/me/accounts' , accessToken );
def call_api ( endpoint : str , access_token : str ):
response = requests.get(
f 'https://api.aries.com { endpoint } ' ,
headers = { 'Authorization' : f 'Bearer { access_token } ' },
timeout = 10 ,
)
if response.status_code == 401 :
raise Exception ( 'Token expired or invalid — refresh or re-authenticate' )
if response.status_code == 403 :
raise Exception ( 'Insufficient permissions — check granted scopes' )
response.raise_for_status()
return response.json()
accounts = call_api( '/v1/users/me/accounts' , access_token)
Step 5: Refresh the access token
Access tokens expire after expires_in seconds (typically 1 hour). Use the refresh token to obtain a new access token without requiring the user to log in again. Refresh proactively before expiry.
Endpoint: POST https://api.aries.com/v1/oauth2/token
Request body:
Field Required Description client_idYes Your OAuth2 client ID client_secretYes Your OAuth2 client secret grant_typeYes refresh_tokenrefresh_tokenYes The refresh token from the initial exchange redirect_uriYes Must match the redirect URI used in the original flow
curl -X POST 'https://api.aries.com/v1/oauth2/token' \
-H 'Content-Type: application/json' \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "refresh_token",
"refresh_token": "YOUR_REFRESH_TOKEN",
"redirect_uri": "YOUR_REDIRECT_URI"
}'
async function refreshAccessToken ( refreshToken ) {
const response = await fetch ( 'https://api.aries.com/v1/oauth2/token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
client_id: process . env . ARIES_CLIENT_ID ,
client_secret: process . env . ARIES_CLIENT_SECRET ,
grant_type: 'refresh_token' ,
refresh_token: refreshToken ,
redirect_uri: process . env . ARIES_REDIRECT_URI ,
}),
});
if ( ! response . ok ) throw new Error ( `Token refresh failed: ${ response . status } ` );
const tokens = await response . json ();
// Store new tokens — a new refresh_token may be issued
return tokens ;
}
def refresh_access_token ( refresh_token ):
response = requests.post(
'https://api.aries.com/v1/oauth2/token' ,
json = {
'client_id' : os.environ[ 'ARIES_CLIENT_ID' ],
'client_secret' : os.environ[ 'ARIES_CLIENT_SECRET' ],
'grant_type' : 'refresh_token' ,
'refresh_token' : refresh_token,
'redirect_uri' : os.environ[ 'ARIES_REDIRECT_URI' ],
}
)
response.raise_for_status()
return response.json()
The response format is the same as the initial token exchange. If a new refresh_token is returned, persist it — it replaces the previous one.
Complete example (Node.js)
Here is a minimal Express.js server demonstrating the full flow:
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const session = require ( 'express-session' );
const app = express ();
app . use ( session ({ secret: 'your-session-secret' , resave: false , saveUninitialized: false }));
// Step 1: Redirect to Aries
app . get ( '/login' , ( req , res ) => {
const state = crypto . randomBytes ( 32 ). toString ( 'hex' );
req . session . oauth_state = state ;
const params = new URLSearchParams ({
response_type: 'code' ,
client_id: process . env . ARIES_CLIENT_ID ,
redirect_uri: process . env . ARIES_REDIRECT_URI ,
scope: 'user:information account:information' ,
state ,
});
res . redirect ( `https://app.aries.com/oauth2/authorize? ${ params } ` );
});
// Step 2 & 3: Callback and token exchange
app . get ( '/oauth/callback' , async ( req , res ) => {
if ( req . query . state !== req . session . oauth_state ) {
return res . status ( 400 ). send ( 'Invalid state' );
}
if ( req . query . error ) {
return res . status ( 400 ). send ( `Error: ${ req . query . error } ` );
}
const response = await fetch ( 'https://api.aries.com/v1/oauth2/token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
client_id: process . env . ARIES_CLIENT_ID ,
client_secret: process . env . ARIES_CLIENT_SECRET ,
grant_type: 'code' ,
code: req . query . code ,
redirect_uri: process . env . ARIES_REDIRECT_URI ,
}),
});
if ( ! response . ok ) {
return res . status ( 500 ). send ( 'Token exchange failed' );
}
const tokens = await response . json ();
req . session . access_token = tokens . access_token ;
req . session . refresh_token = tokens . refresh_token ;
res . redirect ( '/dashboard' );
});
// Step 4: Use the token
app . get ( '/dashboard' , async ( req , res ) => {
if ( ! req . session . access_token ) {
return res . redirect ( '/login' );
}
const accounts = await fetch ( 'https://api.aries.com/v1/users/me/accounts' , {
headers: { 'Authorization' : `Bearer ${ req . session . access_token } ` },
}). then ( r => r . json ());
res . json ( accounts );
});
Next steps
OAuth2 Overview Scopes, security, rate limits, and troubleshooting.
PKCE Flow Implement OAuth2 for SPAs and mobile apps.
Token API Reference Interactive token endpoint reference.
Quick Start Get started in minutes.