AIRisk API OAuth2 Authentication
Describing Oauth2 and, more specifically, our authentication process in AIRisk, Prescribing usage, and Explaining our components and process.
Overview
The AIRisk API uses OAuth 2.0 – the industry-standard protocol for authorization – to allow third-party applications to access its resources securely (see the OAuth 2.0 spec RFC 6749 and a high-level OAuth2 overview for more information).
In essence, OAuth2 enables your application (the client) to obtain limited access to a resource owner's account (the user) without sharing the user's credentials. It works by delegating user authentication to the service that hosts the user account (the AIRisk platform) and then authorizing the client to access resources on the user's behalf, as described in this introduction to OAuth 2.0.
AIRisk API implements an Authorization Code Grant flow. This is a two-step process:
Authorization Request: Your application directs the user’s browser to AIRisk’s Authorization Endpoint with your
client_id
, aredirect_uri
, and optionally auserid
if you are requesting a user-specific token. If requesting a user-specific token, theuserid
must be an existingid
on someApplicationUser
in the AIRisk deployment. Also,client_id
andclient_secret
must be valid for someAPIUser
in the instances database.redirect_uri
must match at least one comma-separated value in the sameAPIUser
s (as the client id & secret)redirect_uri
column
Authorization Code Redirect: If AIRisk’s authorization server authorizes the request, then it redirects the client back to the specified
redirect_uri
with an authorization code in the URL (e.g. https://localhost/callback?code=REMOTELY_GENERATED_AUTH_CODE_HERE). This code is short-lived and cannot be used by itself except to request a token.Token Request: Your application then makes a secure POST request to the Token Endpoint, providing the code along with your
client_id
,client_secret
,redirect_uri
, and (if used in step 1) the sameuserid
. This step happens server-to-server (backend of your app to AIRisk API) and does not involve the user’s browser.Token Response: If the request is valid, the AIRisk authorization server responds with a JSON payload containing an access_token (and possibly other details like expiration). This token can then be used by your app to authenticate future API requests to AIRisk (usually by including it in an HTTP Authorization: Bearer header).
(Optional) Error Handling: If any step fails (e.g., missing parameters, invalid code, or user denies access), an error will be returned instead of an access token. Error responses are yielded as HTTP Reponses with a
status_code
int, and areason
string explaining the error. Some endpoints like/api/oauth2
return a response with json bodies containing fields for further understanding and debugging the error.
Note: The AIRisk OAuth2 flow supports two modes of obtaining tokens:
Generic tokens – obtained by omitting the
userid
parameter. These tokens are not tied to any specific end-user; they represent the application’s own access.User-specific tokens – obtained by including a
userid
. These tokens represent an authorization on behalf of a particular user. The following sections detail how to use each endpoint and clarify the differences between these token types.
sequenceDiagram
participant App as Application
participant Browser as User's Browser
participant Auth as AIRisk Auth Server
%% Step 1: Authorization Request initiated by the Application
App->>Browser: Redirect to<br>/authorize?client_id={client_id}&redirect_uri={redirect_uri}&[userid={userid}]
note right of App: Ensure that client_id and client_secret are valid for an APIUser<br>and that redirect_uri matches one of the allowed URIs
%% Step 2: Authorization Code Request via User’s Browser
Browser->>Auth: GET /authorize?<br>client_id={client_id}, redirect_uri={redirect_uri}, [userid={userid}]
alt Authorized Request
Auth-->>Browser: HTTP 302 Redirect<br>Location: {redirect_uri}?code={auth_code}
else Authorization Error
Auth-->>Browser: HTTP Error Response<br>(status_code, reason)
end
%% Step 3: Token Request from Application Backend
Browser->>App: Browser lands on {redirect_uri} with auth_code
App->>Auth: POST /token<br>{ code: {auth_code}, client_id, client_secret, redirect_uri, [userid] }
alt Valid Token Request
Auth-->>App: JSON { access_token, expires_in, ... }
else Token Request Error
Auth-->>App: HTTP Error Response<br>(status_code, reason, json body)
end
Authorization Endpoint (GET /api/oauth2
)
GET /api/oauth2
)The Authorization Endpoint is the URL that the user’s browser visits to initiate the OAuth flow. It is used to request user authorization and produce an authorization code.
Endpoint: GET /api/oauth2
Purpose: Initiate an OAuth2 authorization code request (user login & consent).
Query Parameters:
client_id
(string, Required) – Your application’s client identifier. This value is provided when you register your app with AIRisk. It tells the authorization server which application is making the request.redirect_uri
(string, Required) – The URL in your application where users will be sent after authorization. This must match one of the allowed redirect URIs you configured for your client. After the user approves or denies the request, AIRisk will redirect their browser to this URL with additional parameters (code or error).userid
(string, Optional) – An identifier for the user on whose behalf the authorization is being requested. Include this if you want a user-specific token. When provided, the authorization process will associate the resulting code with the specified user. If omitted, the authorization is not tied to a specific user and will result in a generic token (suitable for application-level access).
Example Authorization URL:
To start the flow, redirect the user’s browser to the authorization endpoint. For example, if your client ID is abc123 and your redirect URI is https://localhost/callback, you would use:
https://api.AIRisk.example.com/api/oauth2?client_id=abc123&redirect_uri=https%3A%2F%2Flocalhost%2Fcallback
If you want a token on behalf of a user (e.g., user ID user_42), include the userid
parameter:
https://api.AIRisk.example.com/api/oauth2?client_id=abc123&redirect_uri=https%3A%2F%2Flocalhost%2Fcallback&userid=user_42
When the user visits this URL, if the client id and user id are valid, the user’s browser will be redirected to:
https://localhost/callback?code=SplxlOBeZQQYbYS6WxSbIA
The query parameter code=SplxlOBeZQQYbYS6WxSbIA
(this is a sample code) is the authorization code that your application will exchange for an access token in the next step. The code is typically a short-lived, single-use string. (If the server denied the request or an error occurred, you might instead receive error parameters in this redirect, or may be redirected to a json response with error parameters).
Error Responses
401 - Unauthorized
For example, if passing in an unknown
client_id
, the endpoint returns a response like:{ "type": "https://tools.ietf.org/html/rfc9110#section-15.5.2", "title": "Unauthorized", "status": 401, "traceId": "00-676e4f60e893d3cf6bb37b9262dda023-7d0ef785dd85a317-00" }
With Parameters:
type
: string url - Link to the RFCtitle
: string - Human-readable name of the errorstatus
: int - The Error CodetraceId
: string - The id of the trace with which to use to check the stack trace later on.
Note: It’s recommended to also include a "state" parameter (in our case:
code
) in the authorization URL (not shown above) to maintain state between the request and callback (protecting against CSRF attacks). The AIRisk authorization server will return this state value unmodified in the redirect. This is part of OAuth2 best practices, though for brevity the examples here focus on the core parameters.
Token Endpoint (POST /api/oauth2/Token
)
POST /api/oauth2/Token
)The Token Endpoint is used by your application to exchange the authorization code for an actual access token. This is a server-to-server request (the user does not interact with this endpoint directly). Your client must authenticate itself when calling this endpoint, typically by providing its client secret, to ensure the request is secure.
Endpoint: POST /api/oauth2/Token
Purpose: Exchange an authorization code for an access token.
Request Parameters (POST body):
client_id
(string, Required) – Your client identifier, as in the authorization step.client_secret
(string, Required) – Your client secret, which is a confidential key provided when you registered your application. This secret proves to the authorization server that the request is coming from your application (and not a malicious attacker who intercepted the code).redirect_uri
(string, Required) – The same redirect URI that was used in the authorization request. It must exactly match the URI originally provided. This is used as an additional security check: the authorization server will ensure the code was issued for this specific redirect URI.code
(string, Required) – The authorization code received from the Authorization Endpoint redirect. This is the value of thecode
query parameter that was passed to your redirect URI. The code has a short expiration time and can only be used once.userid
(string, Optional) – The user identifier, if one was included in the authorization request. You must include the sameuserid
here to obtain a user-specific token. If you omituserid
here (and the code was associated with a user), the token request will fail. Conversely, if nouserid
was used in the authorization step, you should not include one in this request.
Request Format: The token request is an HTTP POST. You can send the parameters as form URL-encoded (Content-Type application/x-www-form-urlencoded
) or as a JSON body. The examples below demonstrate both approaches using curl and Python.
Example Token Request – Generic Token (no userid
)
userid
)Using curl (with form-encoded body):
curl -X POST "https://api.AIRisk.example.com/api/oauth2/Token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=YOUR_client_id&client_secret=YOUR_client_secret&redirect_uri=https%3A%2F%2Flocalhost%2Fcallback&code=AUTH_CODE_HERE"
In this example:
Replace
YOUR_client_id
andYOUR_client_secret
with your actual credentials.redirect_uri
is URL-encoded (https%3A%2F%2Flocalhost%2Fcallback corresponds to https://localhost/callback).AUTH_CODE_HERE
should be replaced with the code you received in the previous step (e.g., SplxlOBeZQQYbYS6WxSbIA).
Alternatively, using Python and the
requests
library (sending JSON payload):import requests token_url = "https://api.AIRisk.example.com/api/oauth2/Token" data = { "client_id": "YOUR_client_id", "client_secret": "YOUR_client_secret", "redirect_uri": "https://localhost/callback", "code": "AUTH_CODE_HERE" } response = requests.post(token_url, json=data) token_response = response.json() print(token_response.get("access_token"))
Here we post a JSON body with the required fields. The server will respond with a JSON object (as shown below). We then parse the JSON to extract the access_token. In a real application, you would store this token and use it to authorize API calls via the HTTP Header for a bearer token.
Example Success Response (JSON):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiration": "2025-03-31T13:12:17Z"
}
access_token
– The OAuth2 access token string. This is typically a long opaque string or JWT that you will use to authenticate requests to the AIRisk API.expiration
– The UTC-formatted datetime in which the token expires.
Example Token Request – User-Specific Token (with userid
)
userid
)If you included a userid
in the authorization step, use the same userid
when exchanging the code. For example, suppose we indicated userid=user_42
in the authorization URL and got back a code. We would make the token request as follows:
curl -X POST "https://api.AIRisk.example.com/api/oauth2/Token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_client_id&client_secret=YOUR_client_secret&redirect_uri=https%3A%2F%2Flocalhost%2Fcallback&code=AUTH_CODE_HERE&userid=user_42"
This is identical to the earlier request, except we have appended &userid=user_42
(using the same user ID that was in the auth request). The response format for a user-specific token is the same JSON structure:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiration": "2025-03-31T14:10:57Z"
}
In this case, access_token
here is associated with User 42’s account. When using this token to call protected endpoints, the AIRisk API will treat the request as on behalf of that user.
Error Responses
If the token request is invalid or the authorization code has expired/been used, the token endpoint will return an error instead of an access token. Error responses use HTTP status codes (like 400 or 401) and an HTTP reason.
For example, if a Required parameter (ex.
client_id
) is missing:HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=utf-8 Date: Thu, 27 Mar 2025 18:58:24 GMT Server: Kestrel Set-Cookie: ARRAffinity=SOME_VALUE;Path=/;HttpOnly;Secure;Domain=demo.aicrisk.com, ARRAffinitySameSite=SOME_OTHER_VALUE;Path=/;HttpOnly;SameSite=None;Secure;Domain=demo.aicrisk.com Transfer-Encoding: chunked Strict-Transport-Security: max-age=2592000 Request-Context: appId=SOME_APP_ID Invalid client_id
Or if the provided
code
is not valid (e.g., already used or expired):HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=utf-8 Date: Thu, 27 Mar 2025 19:17:38 GMT Server: Kestrel Set-Cookie: ARRAffinity=SOME_VALUE;Path=/;HttpOnly;Secure;Domain=demo.aicrisk.com, ARRAffinitySameSite=SOME_VALUE;Path=/;HttpOnly;SameSite=None;Secure;Domain=demo.aicrisk.com Transfer-Encoding: chunked Strict-Transport-Security: max-age=2592000 Request-Context: appId=cid-v1:SOME_APP Invalid Code
Or if the provided Client ID isn't valid: Full HTTP Response:
HTTP/1.1 401 Unauthorized Content-Type: application/problem+json; charset=utf-8 Date: Thu, 27 Mar 2025 19:09:27 GMT Server: Kestrel Set-Cookie: ARRAffinity=SOME_VALUE;Path=/;HttpOnly;Secure;Domain=demo.aicrisk.com, ARRAffinitySameSite=SOME_VALUE;Path=/;HttpOnly;SameSite=None;Secure;Domain=demo.aicrisk.com Transfer-Encoding: chunked Strict-Transport-Security: max-age=2592000 Request-Context: appId=SOME_APP { "type": "https://tools.ietf.org/html/rfc9110#section-15.5.2", "title": "Unauthorized", "status": 401, "traceId": "00-de61877dd04ebafff7c1676b5b6e9532-5debd224217a8b45-00" }
Response Content JSON (ex...)
resp = requests.post(OAUTH_TOKEN_ENDPOINT, data=data) resp.json() print(resp)
{ "type":"https://tools.ietf.org/html/rfc9110#section-15.5.2", "title":"Unauthorized", "status":401, "traceId":"00-b7704abbc12b42d34f405553b712bac7-2885ab6a78ee25ea-00" }
Or if the client_secret is missing
HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=utf-8 Date: Thu, 27 Mar 2025 19:03:52 GMT Server: Kestrel Set-Cookie: ARRAffinity=SOME_VALUE;Path=/;HttpOnly;Secure;Domain=demo.aicrisk.com, ARRAffinitySameSite=SOME_VALUE;Path=/;HttpOnly;SameSite=None;Secure;Domain=demo.aicrisk.com Transfer-Encoding: chunked Strict-Transport-Security: max-age=2592000 Request-Context: appId=SOME_APP_ID Invalid client_secret
Or if the client_secret is invalid
HTTP/1.1 401 Unauthorized Content-Type: application/problem+json; charset=utf-8 Date: Thu, 27 Mar 2025 19:22:06 GMT Server: Kestrel Set-Cookie: ARRAffinity=SOME_VALUE;Path=/;HttpOnly;Secure;Domain=demo.aicrisk.com, ARRAffinitySameSite=SOME_VALUE;Path=/;HttpOnly;SameSite=None;Secure;Domain=demo.aicrisk.com Transfer-Encoding: chunked Strict-Transport-Security: max-age=2592000 Request-Context: appId=cid-v1:SOME_APP { "type": "https://tools.ietf.org/html/rfc9110#section-15.5.2", "title": "Unauthorized", "status": 401, "traceId": "00-c6bf787605465c8897a36d5957f00fb5-fd988d1c79cd27ed-00" }
Each error response with JSON content includes an "status_code" code and a human-readable "reason" (Is only not JSON if entirely not passing in a necessary parameter. Even if invalid, it will return JSON decode-able content). The error responses without JSON content only have their status code number (ex. 404 in
HTTP/1.1 401 Unauthorized
...
Invalid client_secret
), the status name e.g. "Unauthorized", and the error message e.g "Invalid client_secret".
Always check for an error response and handle it gracefully in your application (e.g., in your app interacting with an AIRisk instance, prompt the user to re-authenticate if the code is no longer valid).
Generic vs. User-Specific Tokens
When using the AIRisk API’s OAuth2 flow, you have the flexibility to obtain tokens that are (conceptually) either generic (not tied to any user) or user-specific. The difference comes down to whether you supply the optional userid
parameter in the flow:
Generic Access Tokens (Application-Only, not attached to particular
ApplicationUser
although retrieved using credentials from validAPIUser
): If you do not includeuserid
in the authorization and token requests, the resulting access_token represents your application itself, not any particular user. These tokens are useful for accessing endpoints or performing actions that are not user-specific.For example, accessing public data or using client-wide privileges. The token is still scoped to what your client application is allowed to do, but no specific user's data is directly implicated. In practical terms, when your app uses this token, the AIRisk API will see the request as coming from your application in a general context, only identifying the client by
client_id
which is tied to anAPIUser
in which may be used via arbitraryApplicationUser
instances.In our case, one may access
/api/Users/ListUsers
if using a generic token as the bearer token in the HTTP headers. In this case, one may fetch the list of known user ids and then select one with which to further log in as.
User-Specific Access Tokens (On-Behalf-of User): If you do include a
userid
in the requests to/api/oauth2
and/api/api/oauth2/Token
, the flow will produce a token associated with that user. This typically means the user has explicitly authorized your app to act on their behalf. The token carries the identity or context of the specified user. Use these tokens to access or modify data that the user has access to in the AIRisk API.
Important distinctions:
A generic token might grant only limited, generic scope access, whereas a user token can grant permissions to read or write to database tables which they can access.
You cannot use a generic token to access user-specific endpoints that require a user context. Similarly, a user-specific token only grants access to the scope of their existing access; you would need separate tokens for different users.
However, you may use an a user-specific access token to access any endpoints accessible via just a generic token.
The
userid
parameter should be treated carefully. The user must correspond to an actualApplicationUser
in the AIRisk system, and the authorization server will not ensure that the person approving the request is that user, and therefore provides a means of impersonating other users. (In many OAuth systems, you don't manually specify the user – the user logs in themselves. AIRisk’suserid
parameter might be used in scenarios where the user identity is known ahead of time or to streamline first-party integrations. Always follow AIRisk’s guidelines on how to obtain or use this parameter.)In summary, use a generic token when your app needs to perform application-level actions or maintenance tasks that aren't tied to a single user (such as listing available users). Use a user-specific token when your app is performing actions on behalf of a specific user, such as accessing that user's data or acting in their name within the AIRisk platform.
Example Usage Flow / Reasons
Two-Step Authentication Process
Description: You have an app using the remote AIRisk instance in which only admins / trusted persons use.
Use Case / Needs:
See Available Users
Select One & Authenticate as them
i.e. Impersonation
Access endpoints and/or switch user (authenticating) as needed
Methodology
First, use
CLIENT_ID
&CLIENT_SECRET
from the local environmentInvoke the authentication process for a generic token and store that to be used as the bearer token (for now)
Have a local endpoint that calls the remote endpoint
/api/ListUsers
, collects the returned JSONdata, and then redirects to a "Select User" page, listing the available users names & idsIf having a
USER_ID
in the environment for example, you could have the form default to itCommon practice for this in
flask
would be to redirect to an endpoint for select_user which, when called via a GET request renders the form page (with the form action pointing to the same URL), or when called via a POST request collecting the submitted form data from the same page and then redirecting as needed.
When a user is selected, set that as the active user id in the session and invoke the authentication process again but this time also passing
userid={SELECTED_USER_ID}
as respective parameter types for the respective Authentication endpoints. Then save the returnedaccess_token
as the active bearer token to use in the headers of future GET requests.
Single-User App
Description: You have an App with a custom interface and which interacts with a remote AIRisk project instance. You will be the only person running this program, and only will do so on your own secure device.
Use Case / Needs
Load
APIUser
relatedCLIENT_ID
andCLIENT_SECRET
from a.env
fileLoad
ApplicationUser
relatedid
also from a.env
file in which belongs to your user accountAssuming both of the above exist and are valid.
Methodology
Load from
.env
Store the
USER_ID
environment variable asuserid
in the session / local memory for usage across views/pages.Invoke the authentication process but include
userid
as a parameter for both respective endpoints (encoding it in the URL for the GET request to/api/oauth2
. Passing as a parameter in the POST request to/api/oauth2/Token
)Use the access token as the bearer token across the app.
Usage of the Access Token
Regardless of token type, once you have an access_token, your application will include it in API requests to AIRisk to authenticate. Most commonly, this is done by adding an HTTP header:
Authorization: Bearer <ACCESS_TOKEN>
For example, using a placeholder user token (where /api/GetConversations
is only accessible by users, thus requiring a user token):
curl -H "Authorization: Bearer AAABBBCCC111222333444..." \
"https://demo.aicrisk.com/api/GetConversations"
or in python:
import requests
ACCESS_TOKEN = "AABBCCDD...access token here"
response = requests.get(
"https://demo.aicrisk.com/api/GetConversations",
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}", "Accept": "application/json"
}
)
if response.status_code != 200:
return f"Data Retrieved Invalid: {response.status_code} - {response.reason}", response.status_code
users = response.json() # data retrieved
The AIRisk API will verify the token (and the user context, if any) and then process the request if the token is valid and has the required permissions. If the token is expired or invalid, the API will return an HTTP 401 Unauthorized error (with a status_code
(401) and reason
).
Conclusion
By following the above OAuth2 authorization flow, your application can securely obtain access tokens from AIRisk API. This design ensures that users can grant or revoke access as needed and that your app never needs to handle raw passwords. It also allows AIRisk to scope the issued tokens to specific users or to your application overall, depending on the presence of the userid
parameter.
Furthermore, it allows AIRisk admins to define sets of credentials and allowed redirect uris (corresponding to
APIUser
instances), and then share the client id, secret, and the redirect uris as well as set them accordingly. These are all one needs to be able to get a generic token via the authentication process.Moreover, the admins also have control over the
ApplicationUser
instances (each with a uniqueuserid
, corresponding with an actual user of the platform), the agents they can access, particular chat-related settings, as well as theirCompany
to which they belong to further scope their permissions.
For more information on OAuth2 and best practices, you may refer to external resources like the official OAuth 2.0 framework specification RFC 6749 for in-depth details, or approachable guides such as OAuth 2 Simplified and the DigitalOcean tutorial "An Introduction to OAuth 2" for a conceptual overview. Using this information to properly authenticate and acquire tokens, you may now explore the AIRisk API endpoints with authenticated requests, knowing that the OAuth2 standard is handling the authorization securely under the hood. Happy building!
Last updated