OAuth 2.0 Authentication

Roubler's API is secured using OAuth 2.0 and OpenID Connect.

How OAuth 2.0 Authorization Code Flow Works

At a high-level, the authorization code flow has the following steps:

  1. Your application directs the browser to the authorization page
  2. The user authenticates and approves your application's request
  3. The user is redirected back to your application with an authorization code in the query string
  4. Your application sends this code to exchange for an API access token and a refresh token
  5. Your application can now use these tokens to call the API on behalf of the user

OAuth 2.0 Flow Diagram

OAuth 2.0 Authorization Code Flow

Quick Start: Step-by-Step Authentication Flow

Step 1: Get Your Credentials

Before you begin, you'll need these credentials from Roubler:

  • CLIENT_ID
  • CLIENT_SECRET
  • DOMAIN (specific to your environment)
  • API user credentials (username/password)

Step 2: Choose Your Environment

Select the appropriate environment and domain:

Environment Domain API URL Description
Staging AU oidc.staging.roubler.net https://graphql.au.staging.roubler.net/graphql Sandbox environment for AU region
Staging EU oidc.staging.roubler.net https://graphql.eu.staging.roubler.net/graphql Sandbox environment for EU region
Production AU oidc.roubler.com https://graphql.au.roubler.com/graphql Production environment for AU region
Production EU oidc.roubler.com https://graphql.eu.roubler.com/graphql Production environment for EU region

Step 3: Get Authorization Code

Visit this URL in your browser (replace <DOMAIN> and <CLIENT_ID> with your values):

https://<DOMAIN>/oauth2/auth?response_type=code&client_id=<CLIENT_ID>&redirect_uri=http://localhost/callback&scope=offline_access&state=123456789

  1. Login with your API user credentials
  2. You'll be redirected to http://localhost/callback?code=AUTHORIZATION_CODE&state=123456789
  3. Copy the code parameter from the URL

Important: This documentation uses the state 123456789 as an example. You should use a unique string or UUID for each request.

Step 4: Exchange Code for Tokens

Make a POST request to exchange your authorization code for access and refresh tokens:

curl -X POST "https://<DOMAIN>/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic <BASE64_ENCODED_CLIENT_ID_AND_SECRET>" \
  -d "grant_type=authorization_code" \
  -d "code=<AUTHORIZATION_CODE>" \
  -d "redirect_uri=http://localhost/callback"

To create the Base64 encoded credentials:

echo -n "your_client_id:your_client_secret" | base64

Step 5: Test Your Authentication

Run the viewer query to verify everything is working:

query Viewer {
  viewer {
    user {
      id
      fullName
      email
    }
    employees {
      company {
        id
        name
      }
      location {
        id
        name
      }
    }
  }
}

Step 6: Use Your Access Token

Include your access token in all API requests:

Authorization: Bearer <access_token>
X-Company-ID: <company_id>

Understanding X-Company-ID:

  • Purpose: Tells the API which company's data to access (required for most operations)
  • Where to get it: Run the viewer query to see available companies and their IDs
  • Multi-tenant support: If you have access to multiple companies, you can switch between them by changing this header
  • Always required: Even if you only have access to one company, you must include this header

Getting your company ID:

  1. Run the viewer query from Step 5
  2. Look at the employees[].company.id values in the response
  3. Use one of those company IDs in your X-Company-ID header

Example request:

POST https://graphql.au.staging.roubler.net/graphql
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Company-ID: 789
Content-Type: application/json

{
  "query": "query { employees { id fullName } }"
}

Detailed Configuration

OAuth 2.0 Parameters

Parameter Value Description
Auth URL <DOMAIN>/oauth2/auth Authorization endpoint
Token URL <DOMAIN>/oauth2/token Token exchange endpoint
Callback URL http://localhost/callback Redirect URI (additional URLs can be added on request)
Client ID CLIENT_ID Provided by Roubler
Client Secret CLIENT_SECRET Provided by Roubler
Scope offline_access Required scope for refresh tokens
State Random string/UUID Security parameter (use a unique value for each request)

Token Response

When successful, you'll receive:

{
  "access_token": "your_access_token_here",
  "refresh_token": "your_refresh_token_here",
  "expires_in": 3600,
  "token_type": "Bearer"
}
  • access_token: Use this to authenticate API calls (expires in 1 hour)
  • refresh_token: Use this to get new access tokens (expires in 30 days)
  • expires_in: Time until access token expires (in seconds)
  • token_type: Always "Bearer"

Token Management

Refreshing Access Tokens

When to refresh:

  • Access tokens expire after 1 hour
  • Use your refresh token to get a new access token
  • Each refresh token can only be used once

Manual testing with curl:

curl -X POST "https://<DOMAIN>/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic <BASE64_ENCODED_CLIENT_ID_AND_SECRET>" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=<REFRESH_TOKEN>"

Response:

{
  "access_token": "new_access_token_here",
  "refresh_token": "new_refresh_token_here",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Important: Always store the new refresh token - the old one becomes invalid.

Reactive vs Proactive Token Management

Reactive Approach:

  • Detect when the access token is about to expire and refresh it
  • Monitor token expiration time and refresh before expiry

Proactive Approach:

  • Refresh the token at the start of each operation or session
  • Always ensure you have a fresh token before making API calls

Reactive Implementation (javascript):

// Check if token expires within the next 5 minutes
const tokenExpiryTime = tokenIssuedAt + expiresIn * 1000;
const refreshThreshold = 5 * 60 * 1000; // 5 minutes

if (Date.now() > tokenExpiryTime - refreshThreshold) {
  // Refresh token before it expires
  await refreshAccessToken();
}

Proactive Implementation (javascript):

// Refresh token at the start of each session/operation
async function makeApiCall() {
  await refreshAccessToken(); // Always refresh first
  return await callRoublerAPI();
}

Testing Your Authentication

After obtaining your access token, the first query you should run is the viewer query to verify your authentication and understand your access level.

What is the Viewer Query?

The viewer query returns information about your API user account and the employee records that your API credentials have access to, along with their company and location details. This helps you understand what data you can query and which company/location contexts are available for your integration.

Basic Viewer Query

query Viewer {
  viewer {
    user {
      id
      fullName
      email
    }
    employees {
      company {
        id
        name
      }
      location {
        id
        name
      }
    }
  }
}

Example Response

{
  "data": {
    "viewer": {
      "user": {
        "id": "123",
        "fullName": "API User",
        "email": "api@company.com"
      },
      "employees": [
        {
          "company": {
            "id": "789",
            "name": "Acme Corporation"
          },
          "location": {
            "id": "10101",
            "name": "Head Office"
          }
        }
      ]
    }
  }
}

Error Handling

If the viewer query fails, check:

  1. Authentication: Verify your access token is valid and not expired
  2. Headers: Ensure you're including the Authorization: Bearer <token> header
  3. Token Refresh: If you get a 401 error, refresh your access token

Next Steps

After successfully running the viewer query:

  1. Note your company IDs - Use these for company-specific queries with the X-Company-ID header
  2. Note your location IDs - Use these for location-specific queries with the X-Location-ID header
  3. Test a simple query - Try a basic employee or locations query to verify full API access

Understanding the Response

  • user: Your API user account information
  • employees: All employees you have access to, including their company and location context
  • company: Company information for each employee
  • location: Location information for each employee

Using the Viewer Query

  1. Authentication Verification: If this query succeeds, your authentication is working correctly
  2. Context Discovery: Use the company and location IDs for subsequent API calls
  3. Multi-tenant Setup: If you have access to multiple companies/locations, you can switch contexts

Understanding X-Company-ID and X-Location-ID Headers

Most API endpoints require one of these headers to scope your request:

X-Company-ID (Recommended):

  • Purpose: Scopes requests to a specific company's data
  • Required for: Most API operations
  • Where to get it: Run the viewer query and use the employees[].company.id values
  • Usage: Include as a header: X-Company-ID: <company_id>
  • Recommendation: Use this as your default header for all API requests

X-Location-ID (Use for specific endpoints):

  • Purpose: Scopes requests to a specific location within a company
  • When to use: Some endpoints (like locationSales) don't accept a locationId parameter and require this header instead
  • Where to get it: Run the viewer query and use the employees[].location.id values or query the locations endpoint
  • Usage: Include as a header: X-Location-ID: <location_id>

Which Header Should I Use?

  1. For most endpoints: Use X-Company-ID and pass location-specific IDs as query parameters (e.g., locationId, employeeId)
  2. For endpoints without location parameters: Use X-Location-ID when the query doesn't have a locationId parameter (e.g., locationSales)
  3. If unsure: Check the endpoint documentation's "Implementation Notes" for guidance
  4. Never provide both: Only use one header at a time to avoid confusion

Example Request with X-Company-ID:

POST https://graphql.au.staging.roubler.net/graphql
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Company-ID: 789
Content-Type: application/json

{
  "query": "query { employees { id fullName } }"
}

Example Request with X-Location-ID (For queries without locationId parameter):

POST https://graphql.au.staging.roubler.net/graphql
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Location-ID: 10101
Content-Type: application/json

{
  "query": "query LocationSales($startTime: DateTime!, $endTime: DateTime!) { locationSales(startTime: $startTime, endTime: $endTime) { day forecast actual } }",
  "variables": {
    "startTime": "2024-05-18T16:00:00.000Z",
    "endTime": "2024-05-26T17:59:59.000Z"
  }
}

Common Pitfalls

Token Management Mistakes:

  • Not storing refresh tokens securely
  • Trying to reuse refresh tokens
  • Not handling token refresh failures gracefully

Header Issues:

  • Forgetting the X-Company-ID header
  • Using expired access tokens
  • Incorrect Authorization header format

State Parameter Issues:

  • Using the same state value for multiple requests
  • Not validating the returned state matches the sent state
  • Using predictable state values (use UUIDs)

Troubleshooting

"Invalid client" error:

  • Check your CLIENT_ID is correct
  • Verify you're using the right environment domain

"Invalid grant" error:

  • Authorization code may have expired (they expire after 5 mins)
  • Code may have already been used
  • Check your CLIENT_SECRET is correct

"Invalid redirect_uri" or "redirect_uri_mismatch" error:

  • The callback URL in your request doesn't match what's configured for your client
  • Check you're using exactly http://localhost/callback (or your configured URL)
  • Ensure the URL is registered with Roubler for your CLIENT_ID
  • Common mistakes: using https:// instead of http://, different ports, trailing slashes

"Access denied" on API calls:

  • Verify X-Company-ID header is included
  • Check the company ID exists in your viewer query response
  • Ensure your access token hasn't expired

Security Best Practices

  • Never expose client secrets in client-side code
  • Use HTTPS only for all OAuth endpoints
  • Validate state parameters to prevent CSRF attacks
  • Store tokens securely using encrypted storage
  • Implement proper error handling without exposing sensitive information
  • Use environment variables for credentials in production

Environment-Specific Notes

Staging Environment:

  • Use your staging credentials
  • Use for development and testing
  • Test data may be reset periodically but not wihtout prior notice

Production Environment:

  • Use your production credentials
  • Use production URLs
  • Monitor rate limits carefully
  • Implement proper error monitoring

Frequently Asked Questions

How long does the access token last?
1 hour.

How long does the refresh token last?
30 days or until the refresh token is exchanged for a new access token.

Do I need to re-authenticate/complete 2FA handshake again when tokens expire?
No, you only need to authenticate and complete the 2FA handshake once, when you first request the authorization code in the browser.

How many times can I use my refresh token?
Once. A new refresh token is provided with each new access token.

Can I refresh the access token concurrently with the same refresh token?
No, each refresh token is single-use. Attempting to refresh the access token concurrently with the same refresh token may result in errors or unexpected behavior.

Can I generate and manage multiple access/refresh tokens against my API credentials?
Yes, as long as you store the refresh tokens for each access token, you can manage access/refresh tokens individually.

You can also generate an access/refresh token per user who authenticates against your API credentials. As long as you store the refresh tokens for each access token, you can manage access/refresh tokens individually.

Can we use the client credential flow instead of authorization code?
No, we currently only support the authorization code flow.

Can you add a new callback url?
Yes, contact Roubler to add additional URLs.

What are your rate limits?
100 calls per 60 seconds.

Our rate limit, remaining calls and time till reset is returned in the headers. We are currently set to monitoring mode meaning we will not block any subsequent calls over these limits. This is subject to change.