> ## Documentation Index
> Fetch the complete documentation index at: https://finance.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorization Code Flow

> Server-side OAuth2 implementation using client_secret. Use for web backends, CLI tools, and apps that can securely store credentials.

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](https://app.aries.com) 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

<Warning>
  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.
</Warning>

***

## 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_type` | Must be `code`                                                           |
| `client_id`     | Your OAuth2 client ID                                                    |
| `redirect_uri`  | Must exactly match a registered URI                                      |
| `scope`         | Space-separated scopes (e.g., `account:information order:execution`)     |
| `state`         | Cryptographically random string for CSRF protection — verify on callback |

<CodeGroup>
  ```javascript Node.js theme={null}
  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);
  ```

  ```python Python theme={null}
  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)
  ```
</CodeGroup>

<Note>
  Always generate a new, cryptographically random `state` for each authorization request and verify it when the user returns. This prevents CSRF attacks.
</Note>

***

## 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                                                                  |
| --------- | ---------------------------------------------------------------------------- |
| `code`    | Authorization code — valid for 10 minutes, single-use. Exchange immediately. |
| `state`   | Must match the value you sent. Verify before proceeding.                     |

<CodeGroup>
  ```javascript Node.js theme={null}
  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');
    }
  });
  ```

  ```python Python theme={null}
  @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
  ```
</CodeGroup>

***

## 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_id`     | Yes      | Your OAuth2 client ID                      |
| `client_secret` | Yes      | Your OAuth2 client secret                  |
| `grant_type`    | Yes      | `code`                                     |
| `code`          | Yes      | The authorization code from the callback   |
| `redirect_uri`  | Yes      | Must match the redirect URI used in Step 1 |

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript Node.js theme={null}
  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();
  }
  ```

  ```python Python theme={null}
  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()
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "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
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET 'https://api.aries.com/v1/users/me/accounts' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```

  ```javascript Node.js theme={null}
  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);
  ```

  ```python Python theme={null}
  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)
  ```
</CodeGroup>

***

## 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_id`     | Yes      | Your OAuth2 client ID                                 |
| `client_secret` | Yes      | Your OAuth2 client secret                             |
| `grant_type`    | Yes      | `refresh_token`                                       |
| `refresh_token` | Yes      | The refresh token from the initial exchange           |
| `redirect_uri`  | Yes      | Must match the redirect URI used in the original flow |

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript Node.js theme={null}
  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;
  }
  ```

  ```python Python theme={null}
  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()
  ```
</CodeGroup>

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:

```javascript Node.js theme={null}
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

<CardGroup cols={2}>
  <Card title="OAuth2 Overview" icon="shield-halved" href="/api-reference/oauth2/guide">
    Scopes, security, rate limits, and troubleshooting.
  </Card>

  <Card title="PKCE Flow" icon="mobile-screen" href="/api-reference/oauth2/pkce-flow">
    Implement OAuth2 for SPAs and mobile apps.
  </Card>

  <Card title="Token API Reference" icon="key" href="/api-reference/oauth2/token">
    Interactive token endpoint reference.
  </Card>

  <Card title="Quick Start" icon="bolt" href="/api-reference/quickstart">
    Get started in minutes.
  </Card>
</CardGroup>
