API authentication
Forge ID exposes a REST API at https://cri.epita.fr/api/v2/. The list of endpoints, their parameters and their responses are documented in the Swagger UI (or the ReDoc version).
This page explains how to authenticate your application against that API with
an OIDC access token (Authorization: Bearer ...) instead of sending a login
and a password on every request.
For security reasons, authenticating on the API with a login and a password (HTTP Basic authentication) is being phased out and will be removed. Access tokens, as described on this page, will then be the only way to authenticate on the API.
If one of your applications still sends credentials on every request, please migrate it now. We will announce the removal date in advance, and you can contact us if you need help planning the migration.
Using a token means your application never stores nor transmits a user password. Tokens are short-lived, limited to the scopes you were granted, and they keep working if the user signs in with Office365 or any other authentication method we may require in the future (such as a second factor). Authentication methods based on a password cannot offer any of these guarantees.
Overview
The API accepts an access token issued by the Forge ID OIDC provider. Getting one follows the regular authorization code flow described in the OIDC page: your application sends the user to Forge ID, the user signs in and approves your application, and you exchange the resulting code for a token.
Your application never sees the user credentials: everything happens on https://cri.epita.fr, which is also why this flow keeps working whatever the sign-in method of the user is.
Requirements
An access token is accepted by the API when all of the following are true:
- it was issued by https://cri.epita.fr;
- it has not expired (access tokens are valid for one hour);
- the account it belongs to is still active;
- it includes the
apiscope.
api scope is not granted by defaultThe api scope is what allows a client to query the API on behalf of a user.
It must be enabled on your client, so please mention it (and what you intend to
do with it) when you request a client. The
public test client can request it during your
development phase.
Endpoints
| Purpose | URL |
|---|---|
| Discovery | https://cri.epita.fr/.well-known/openid-configuration |
| Authorization | https://cri.epita.fr/authorize |
| Token | https://cri.epita.fr/token |
| User info | https://cri.epita.fr/userinfo |
| API | https://cri.epita.fr/api/v2/ |
Whenever possible, read these from the discovery document rather than hardcoding them.
Getting a token
1. Send the user to the authorization endpoint
https://cri.epita.fr/authorize
?client_id=<CLIENT_ID>
&response_type=code
&scope=openid%20api
&redirect_uri=<REDIRECT_URI>
&state=<RANDOM_STATE>
Add any other scope your application needs (profile, email, ...) to the
scope parameter. The redirect_uri must be one of the URIs registered on
your client.
The user signs in and is asked to approve your application. Forge ID then
redirects to your redirect_uri with a code parameter. Always check that the
state you receive is the one you sent.
2. Exchange the code for a token
curl https://cri.epita.fr/token \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d grant_type=authorization_code \
-d code="<CODE>" \
-d redirect_uri="<REDIRECT_URI>"
{
"access_token": "a1b2c3...",
"refresh_token": "d4e5f6...",
"token_type": "bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOi...",
"scope": "openid api"
}
3. Call the API
curl https://cri.epita.fr/api/v2/users/ \
-H "Authorization: Bearer a1b2c3..."
In Python:
import requests
response = requests.get(
"https://cri.epita.fr/api/v2/users/",
headers={"Authorization": f"Bearer {access_token}"},
timeout=10,
)
response.raise_for_status()
print(response.json())
4. Renew the token
Access tokens expire after one hour. Use the refresh token you received to get a new one, without involving the user again:
curl https://cri.epita.fr/token \
-u "<CLIENT_ID>:<CLIENT_SECRET>" \
-d grant_type=refresh_token \
-d refresh_token="<REFRESH_TOKEN>"
Each refresh returns a new access token and a new refresh token: store the new refresh token, the previous one is no longer usable.
A refresh token grants access to the user data for as long as it is valid. Store it like you would store a password: encrypted at rest, never in a repository, never in a URL and never in client-side code. If you suspect one has leaked, tell us so that we can revoke it.
Permissions
A token acts on behalf of the user who approved it. The API applies exactly
the same permissions as the web interface: the api scope opens the door, but
it never grants more rights than the user already has.
In practice this means an endpoint can answer 403 even with a perfectly valid
token, simply because that user is not allowed to read or write this particular
resource.
Error reference
When authentication fails, the API answers with a detail field:
detail | Meaning |
|---|---|
invalid_token | Unknown token. Check that you send the access_token value. |
expired_token | The token is older than one hour, refresh it. |
user_is_not_allowed | The account is disabled. |
insufficient_scope | The token was issued without the api scope. |
A request sent without any Authorization header is treated as anonymous and
is rejected on the permission check instead.
Scripts and automated jobs
The authorization code flow also works for command line tools: run it once with
a loopback redirect URI (for example http://localhost:8000/callback) to open
a browser, then keep the refresh token in the local configuration of the tool.
The client_credentials grant is not supported: every token is tied to a user
account. If you need an unattended job that is not tied to a human account,
contact us so that we can look at your use case together.
Reviewing authorized applications
Users can see the applications they approved and the data these applications
can read on their profile page, under the OIDC tab
(https://cri.epita.fr/users/<login>/oidc/). Removing an application from that
list means it will have to ask for approval again the next time a user signs in
with it.