ARES for Partners
  • ARES OAuth Integration Guide
  • Reference
    • Configuring OAuth
      • Auth0 by Okta
      • Clerk
      • Supabase
      • Manual Integration
    • Configuring App to Earn Credits
      • Track Usage with Clerk
      • Track Usage with Auth0
      • Track Usage with Supabase
  • API Endpoints
Powered by GitBook
On this page
  • Overview
  • Step 1: Get your Auth0 Management API Access Token
  • Follow the official Auth0 guide on how to retrieve an Auth0 Management API Access Token:
  • Step 2: Create an API Route Handler
  • Configure .env file
  • Configure API route
  • GET Method (Access User Information)
  • Step 3: Track user's usage in your app using the API
  • POST Method (Logs and Tracks User's Usage to Earn Credits)
  1. Reference
  2. Configuring App to Earn Credits

Track Usage with Auth0

*Example with NextJS

PreviousTrack Usage with ClerkNextTrack Usage with Supabase

Last updated 2 months ago

Overview

Step 1: Get your Auth0 Management API Access Token

Step 2: Create an API Route Handler

Step 3: Track user's usage in your app using the API

Step 1: Get your Auth0 Management API Access Token

  • You need an that includes the read:users, read:user_idp_tokens update:users update:users_app_metadata scopes.

  • We will use this token to make a HTTP GET call to the to retrieve the ARES access token in Step 2.

Follow the official Auth0 guide on how to retrieve an Auth0 Management API Access Token:

When following the guide, select the application with these scopes: read:users, read:user_idp_tokens update:users update:users_app_metadata

This part corresponds to the highlighted code chunk in Step 2.

Don't expose this token to the client-side of your application!

Step 2: Create an API Route Handler

Configure .env file

Add these 3 variables to your .env file.

Change all values to your own app's values.

.env
ARES_CLIENT_ID="YOUR_ARES_CLIENT_ID"
ARES_CLIENT_SECRET="YOUR_ARES_CLIENT_SECRET"
ARES_REDIRECT_URI="https://dev-a3r31fmnmhcsjwav.us.auth0.com/login/callback" # Example

AUTH0_APP_URL='https://dev-a3r31fmnmhcsjwav.us.auth0.com' # Example only
AUTH0_CLIENT_ID='YOUR_AUTH0_CLIENT_ID'
AUTH0_CLIENT_SECRET='YOUR_AUTH0_CLIENT_SECRET'

# ... pre-existing environment variables ...

Configure API route

Copy and paste the following file into your app/api/ARES/route.ts file.

This route will have a GET method and a POST method.

Always keep this route server-side only.

app/api/ARES/route.js
var axios = require('axios').default;
import { getSession } from '@auth0/nextjs-auth0';
import { NextResponse } from 'next/server';

// Get the ARES access token
async function getAresAccessToken(userId) {
  try {
    // Get Auth0 Management API access token
    const tokenResponse = await axios.request({
      method: 'POST',
      url: `${process.env.AUTH0_APP_URL}/oauth/token`,
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      data: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: process.env.AUTH0_CLIENT_ID,
        client_secret: process.env.AUTH0_CLIENT_SECRET,
        audience: `${process.env.AUTH0_APP_URL}/api/v2/`
      })
    });

    // Get user data using the access token
    const userData = await axios.request({
      method: 'GET',
      url: `${process.env.AUTH0_APP_URL}/api/v2/users/${userId}`,
      headers: { authorization: `Bearer ${tokenResponse.data.access_token}` }
    });

    // Always check for fresh tokens in identities after a re-login
    const aresIdentity = userData.data.identities.find(identity => identity.connection === 'ARES');
    const aresTokensFromMetadata = userData.data.app_metadata?.ares_tokens;

    // Check if the identity has tokens (either first login or re-login)
    if (aresIdentity?.access_token) {
      // If there are no tokens in metadata or the access token in identity is different
      // from the one in metadata, update the metadata with the new tokens
      if (!aresTokensFromMetadata || aresIdentity.access_token !== aresTokensFromMetadata.access_token) {
        console.log('Updating ARES tokens in metadata after sign-in');
        // Store tokens in app_metadata because the tokens in identities can't be updated
        await axios.request({
          method: 'PATCH',
          url: `${process.env.AUTH0_APP_URL}/api/v2/users/${userId}`,
          headers: {
            Authorization: `Bearer ${tokenResponse.data.access_token}`,
            'Content-Type': 'application/json'
          },
          data: {
            app_metadata: {
              ares_tokens: {
                access_token: aresIdentity.access_token,
                refresh_token: aresIdentity.refresh_token
              }
            }
          }
        });
        return {
          access_token: aresIdentity.access_token,
          refresh_token: aresIdentity.refresh_token
        };
      }
    }

    return aresTokensFromMetadata || {};
  } catch (error) {
    console.error('Error getting ARES access token:', error);
    throw error;
  }
}

// Refresh the ARES access token
async function refreshAresToken(refreshToken, userId) {
  try {
    const response = await fetch('https://oauth.joinares.com/oauth/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        client_id: process.env.ARES_CLIENT_ID,
        client_secret: process.env.ARES_CLIENT_SECRET,
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        redirect_uri: process.env.ARES_REDIRECT_URI
      })
    });

    const newTokens = await response.json();

    // Check if the refresh token request returned an error
    if (newTokens.error) {
      console.error('Refresh token error:', newTokens.error, newTokens.error_description);
      throw new Error(`ARES refresh token error: ${newTokens.error_description || newTokens.error}`);
    }

    // Get Auth0 Management API access token to update metadata
    const tokenResponse = await axios.request({
      method: 'POST',
      url: `${process.env.AUTH0_APP_URL}/oauth/token`,
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      data: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: process.env.AUTH0_CLIENT_ID,
        client_secret: process.env.AUTH0_CLIENT_SECRET,
        audience: `${process.env.AUTH0_APP_URL}/api/v2/`
      })
    });

    // Update tokens in app_metadata
    await axios.request({
      method: 'PATCH',
      url: `${process.env.AUTH0_APP_URL}/api/v2/users/${userId}`,
      headers: {
        Authorization: `Bearer ${tokenResponse.data.access_token}`,
        'Content-Type': 'application/json'
      },
      data: {
        app_metadata: {
          ares_tokens: {
            access_token: newTokens.access_token,
            refresh_token: newTokens.refresh_token
          }
        }
      }
    });

    return newTokens;
  } catch (error) {
    console.error('Error refreshing ARES token:', error);
    // Suggest the user to re-authenticate
    throw new Error(
      `Failed to refresh ARES token. Please try signing out and signing in again. Error: ${error.message}`
    );
  }
}

// Fetch the ARES user data
async function fetchARESUserData(accessToken, refreshToken, userId) {
  try {
    const ARESUrl = 'https://oauth.joinares.com/v1/user';
    let response = await fetch(ARESUrl, {
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    });

    if (response.status === 403 || response.status === 401) {
      // Token expired, refresh and retry
      try {
        const newTokens = await refreshAresToken(refreshToken, userId);
        response = await fetch(ARESUrl, {
          headers: {
            Authorization: `Bearer ${newTokens.access_token}`
          }
        });
      } catch (refreshError) {
        console.error('Error refreshing token during API call:', refreshError);
        throw refreshError; // Let the main API handler deal with this error
      }
    }

    if (!response.ok) {
      throw new Error(`ARES API error: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching ARES user data:', error);
    throw error;
  }
}

export async function GET(request) {
  try {
    const session = await getSession(request);
    const userId = session?.user?.sub;

    if (!userId) {
      return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
    }

    const aresTokens = await getAresAccessToken(userId);

    if (!aresTokens.access_token) {
      return NextResponse.json(
        {
          error: 'ARES access token not found',
          requiresReauth: true
        },
        { status: 401 }
      );
    }

    try {
      const aresData = await fetchARESUserData(aresTokens.access_token, aresTokens.refresh_token, userId);
      return NextResponse.json(aresData);
    } catch (error) {
      // Special handling for token errors
      if (
        error.message.includes('ARES refresh token error') ||
        error.message.includes('Failed to refresh ARES token')
      ) {
        return NextResponse.json(
          {
            error: error.message,
            requiresReauth: true
          },
          { status: 401 }
        );
      }
      throw error; // Re-throw other errors to be caught by outer catch
    }
  } catch (error) {
    console.error('Error in GET /api/ARES:', error);
    return NextResponse.json({ error: error.message }, { status: error.response?.status || 500 });
  }
}

export async function POST(request) {
  try {
    const session = await getSession(request);
    const userId = session?.user?.sub;

    if (!userId) {
      return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
    }

    const aresTokens = await getAresAccessToken(userId);

    if (!aresTokens.access_token) {
      return NextResponse.json(
        {
          error: 'ARES access token not found',
          requiresReauth: true
        },
        { status: 401 }
      );
    }

    // Parse request body
    const { usage, credits } = await request.json();

    try {
      const UsageUrl = 'https://oauth.joinares.com/v1/partner/usage';
      let ARESResponse = await fetch(UsageUrl, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${aresTokens.access_token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          client_id: process.env.ARES_CLIENT_ID,
          usage,
          credits
        })
      });

      if (ARESResponse.status === 403 || ARESResponse.status === 401) {
        // Token expired, refresh and retry
        try {
          const newTokens = await refreshAresToken(aresTokens.refresh_token, userId);
          ARESResponse = await fetch(UsageUrl, {
            method: 'POST',
            headers: {
              Authorization: `Bearer ${newTokens.access_token}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              client_id: process.env.ARES_CLIENT_ID,
              usage,
              credits
            })
          });
        } catch (refreshError) {
          // Special handling for token errors
          if (
            refreshError.message.includes('ARES refresh token error') ||
            refreshError.message.includes('Failed to refresh ARES token')
          ) {
            return NextResponse.json(
              {
                error: refreshError.message,
                requiresReauth: true
              },
              { status: 401 }
            );
          }
          throw refreshError;
        }
      }

      if (!ARESResponse.ok) {
        return NextResponse.json({ error: `ARES API error: ${ARESResponse.status}` }, { status: ARESResponse.status });
      }

      const ARESData = await ARESResponse.json();
      return NextResponse.json(ARESData);
    } catch (error) {
      // Special handling for token errors
      if (
        error.message.includes('ARES refresh token error') ||
        error.message.includes('Failed to refresh ARES token')
      ) {
        return NextResponse.json(
          {
            error: error.message,
            requiresReauth: true
          },
          { status: 401 }
        );
      }
      throw error;
    }
  } catch (error) {
    console.error('Error in POST /api/ARES:', error);
    return NextResponse.json({ error: error.message }, { status: error.response?.status || 500 });
  }
}

GET Method (Access User Information)

Calling GET Method

With a user signed in with ARES, you can call the API endpoint you just configured to access the user's data from ARES's server.

Example of getting the user's information through a NextJS client component:

const [userData, setUserData] = useState(null);
const [error, setError] = useState(null);

  useEffect(() => {
    const fetchAresUser = async () => {
      try {
        const response = await fetch('/api/ARES');
        if (!response.ok) {
          throw new Error('Failed to fetch ARES user data');
        }
        const data = await response.json();
        setUserData(data.user);
      } catch (err) {
        setError(err.message);
        console.error('Error fetching ARES user data:', err);
      }
    };

    fetchAresUser();
  }, []);

GET Response

A GET request to your /api/ARES endpoint should return the user's information in json format, including the number of credits the user has left.

Example response
{
  message: 'Success',
  user: {
    id: 'f7133790-9d1a-4afc-a510-f069675d4b15',
    email: 'example@gmail.com',
    name: '',
    credits: 22
  }
}

Step 3: Track user's usage in your app using the API

POST Method (Logs and Tracks User's Usage to Earn Credits)

IMPORTANT: If you check whether or not a user has a subscription before allowing them to use a certain feature on your app, make sure to disable that for users signed in with ARES in order for them to be able to pay-per-use with their ARES credits without having a subscription to your service.

Calling POST Method

Make sure to call this method and check the response success message BEFORE you let your user use the service. This message logs the usage on ARES's server, keeping track of the ARES credits you've earned. Once you have called this method and receive a success message, allow the user to continue.

Only call the API for users who signed in with ARES, otherwise the API will return an error.

Here's an example of a React button that earns 1 ARES credit for your app (and deducts the credit from the user's account).

"use client";

import React from "react";

const handleAresUsageUpdate = async () => {
  try {
  // Make sure to configure this API route first (at the beginning of this page).
    const response = await fetch("/api/ARES", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        usage: "test-button", // Replace with actual usage reason. Must be consistent across usages. (e.g. every time the user uses the test button, use value "test-button", every time the user uses a different button, use value "different-button".
        credits: 1, // Replace with actual credits value
      }),
    });

    if (!response.ok) {
      throw new Error("Failed to update ARES usage");
    }

    const data = await response.json();
  
  } catch (error) {
  }
};


const UsageButton = () => {
  return (
    <button
      onClick={handleAresUsageUpdate}
    >
      Update Usage
    </button>
  );
};

export default UsageButton;

Then, you can make an HTTP GET call to the to retrieve the tokens. We'll do this in the route handler in Step 2.

Get a User endpoint
access token for the Management API
Get a User endpoint
Get Management API Access Tokens for ProductionAuth0 Docs
Make sure to select all scopes: read:users, read:user_idp_tokens update:users update:users_app_metadata
Logo