OAuth 2.0 Client Integration
OAuth 2.0 lets your application act on behalf of a Legalesign user. The user logs in and explicitly consents. Your application receives a short-lived JWT access token tied to their identity, which the GraphQL API accepts.
Use OAuth when your integration needs to act as a specific user. For example, sending documents as a particular team member, or building an app where each user connects their own Legalesign account.
OAuth 2.0 tokens work with the GraphQL API only, and only for the operations that API keys support. See the API-key GraphQL reference for the list. The REST API rejects OAuth tokens.
Prerequisites
- Log in to the Legalesign Developer Portal as an organisation administrator with developer tools enabled
- Go to OAuth Clients
- Select your organisation
- Create a client: give it a name and add your redirect URI(s)
- Copy the client ID and the client secret
The client secret is shown once, when you create the client or rotate its secret. Store it securely on your server. If you lose it, rotate the secret from the developer portal. Never put it in a browser or a mobile app.
Redirect URIs must match exactly. Use https:// for production. http://localhost and http://127.0.0.1 are allowed for development. Redirect URIs cannot contain a fragment (#), credentials (user:password@), whitespace or wildcards.
Only members of your organisation can authorise your client.
The flow
Your server User browser Legalesign
| | |
|-- 1. Build /auth URL ------>| |
| |-- 2. Login + consent ->|
| |<- 3. Redirect + code --|
|<-- 4. Receive code ---------| |
|-- 5. Exchange code for tokens (client secret + PKCE) >|
|<-- 6. access_token + refresh_token ----------------- |
|-- 7. Call GraphQL API with Bearer token ------------>|
Step 1 — Build the authorisation URL
Redirect the user to:
https://oauth.legalesign.com/auth
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid%20profile%20email%20offline_access
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
&prompt=consent
&state=RANDOM_STATE
| Parameter | Value |
|---|---|
response_type | code |
client_id | Your client ID from the developer portal |
redirect_uri | Must exactly match a registered redirect URI |
scope | openid profile email offline_access. Include offline_access to receive a refresh token |
code_challenge | Base64url(SHA256(code_verifier)), see PKCE below |
code_challenge_method | S256 |
prompt | consent. Needed, together with offline_access, to receive a refresh token |
state | A random value you generate; returned unchanged to prevent CSRF |
Tokens are issued for the GraphQL API (https://graphql.uk.legalesign.com). You do not need to send a resource parameter. If you do, it must be that URL, otherwise the request is rejected with invalid_target.
Generating PKCE values
PKCE (Proof Key for Code Exchange) is required. It is used in addition to the client secret. Generate the values server-side before building the URL:
// Node.js
import { randomBytes, createHash } from 'node:crypto';
const codeVerifier = randomBytes(32).toString('base64url');
const codeChallenge = createHash('sha256')
.update(codeVerifier)
.digest('base64url');
# Python
import secrets
import hashlib
import base64
code_verifier = secrets.token_urlsafe(32)
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
Store code_verifier in the user's server-side session. You need it in step 4.
Step 2 — User logs in and consents
Legalesign handles authentication. The user logs in with their Legalesign account, including two-factor authentication if they have it set up, and is shown a consent screen. The screen names your application and your organisation, shows which account is signing in, and lists what the application will be able to do. After approval, they are redirected back to your redirect_uri.
If the user is not allowed to use your application, for example because they are not a member of your organisation, they are redirected back with error=access_denied.
Step 3 — Receive the authorisation code
Your callback endpoint receives:
https://yourapp.com/callback?code=AUTH_CODE&state=YOUR_STATE
Verify that state matches what you sent. If it doesn't, reject the request. It could be a CSRF attempt.
The code is valid for 60 seconds and can be used once.
Step 4 — Exchange the code for tokens
Authenticate to the token endpoint with your client secret, using HTTP Basic authentication (recommended):
curl https://oauth.legalesign.com/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code=AUTH_CODE \
-d redirect_uri=https://yourapp.com/callback \
-d code_verifier=YOUR_CODE_VERIFIER
As an alternative to the Authorization header, you can send client_id and client_secret as form fields in the request body. Do not send both.
// Node.js
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const response = await fetch('https://oauth.legalesign.com/token', {
method: 'POST',
headers: {
Authorization: `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://yourapp.com/callback',
code_verifier: codeVerifier,
}),
});
const tokens = await response.json();
Response:
{
"access_token": "eyJ...",
"refresh_token": "Rj7...",
"id_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600
}
A missing or wrong client secret is rejected with HTTP 401 and invalid_client.
You receive a refresh_token only if you requested the offline_access scope and sent prompt=consent.
Store the tokens securely, server-side only. Never expose them to a browser.
Step 5 — Call the GraphQL API
Include the access token as a Bearer token on every request:
POST https://graphql.uk.legalesign.com/graphql
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{"query": "{ user { id email } }"}
// Node.js example
const response = await fetch('https://graphql.uk.legalesign.com/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: '{ user { id email } }' }),
});
const data = await response.json();
Only the operations listed in the API-key GraphQL reference are available. A token acts as the person who authorised it, with that person's permissions.
Step 6 — Refresh the access token
The access token expires after one hour. Use the refresh token to get a new one without asking the user to log in again. You authenticate with your client secret again:
curl https://oauth.legalesign.com/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=YOUR_REFRESH_TOKEN
The response has the same shape as step 4. Replace both tokens immediately. Refresh tokens rotate on every use. If a refresh token that has already been used is presented again, the request fails with invalid_grant and the whole authorisation is revoked, including the newer token. The user has to authorise again.
How long an authorisation lasts
| Lifetime | |
|---|---|
| Access token | 1 hour |
| Refresh token | 30 days since it was last used. Each refresh issues a new one |
| The user's authorisation | 12 months in total, however often you refresh |
When a refresh fails with invalid_grant, because the refresh token went unused for 30 days, the 12 months are up, or the user's access was removed, send the user through the flow again from step 1.
A refresh also fails if the user has been deactivated or removed from your organisation, or if your client has been revoked.
Revoking a token
To revoke a refresh token yourself, for example when a user disconnects your app, authenticate with your client secret and call the revocation endpoint:
curl https://oauth.legalesign.com/token/revocation \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d token=YOUR_REFRESH_TOKEN
Revoking the refresh token also ends that authorisation, so it can no longer be refreshed. An access token that has already been issued stays valid until it expires, at most an hour. It stops working sooner if the user is removed from your organisation, because membership is checked on every API call.
An organisation administrator can revoke the whole client in the developer portal. API calls with that client's tokens stop working within about five minutes, and no token can be refreshed.
Token claims
The JWT access token contains:
| Claim | Description |
|---|---|
sub | The user's identifier |
username | The user's Legalesign username |
org_id | The organisation your client is registered under |
client_id | Your OAuth client ID |
twofactor | Whether the user has two-factor authentication set up |
scope | The scopes granted |
iss | https://oauth.legalesign.com |
aud | https://graphql.uk.legalesign.com |
exp, iat | Expiry and issue time (Unix timestamps) |
jti | Unique token identifier |
You can decode the token to read these claims without an additional API call. To verify the signature, use the JWKS endpoint:
https://oauth.legalesign.com/jwks
Two-factor authentication
Users log in with the same Legalesign login as the web app. If a team requires two-factor authentication, an OAuth user in that team needs it too, as in the web app, and requests are rejected with MFAREQUIRED otherwise. Users who sign in through single sign-on are treated as they are in the web app.
Discovery
Standard OIDC discovery is available:
https://oauth.legalesign.com/.well-known/openid-configuration
Use this endpoint with any standard OAuth 2.0 / OIDC client library to configure the integration automatically. The token endpoint supports client_secret_basic and client_secret_post.
Security notes
- Server-side only. Your client secret, access tokens and refresh tokens must never reach a browser, a mobile app or a log. Clients are confidential: there are no public clients.
- PKCE is required on every authorisation:
code_challenge_method=S256. - Use
prompt=consentwithoffline_accessif you want a refresh token. - Rotate refresh tokens correctly. Each use issues a new refresh token. Store it and discard the previous one immediately.
- Rotate your client secret from the developer portal if it may have been exposed. The old secret can keep working for up to five minutes after a rotation.
- Rate limits. The authorization server limits request rates. If you receive HTTP 429, or a temporary 5xx error, back off and retry later. Do not retry a failing token request in a tight loop.
A token acts as the person who authorised it, with their access rules, so it can do whatever that person can do. It is not currently restricted to your organisation's data. If someone who authorises your application is also a member of another organisation, the application may be able to use that access as well. Register clients only for applications that your organisation trusts with that access.
Fine-grained action scopes (read-only, document-specific, and so on) will be available in a future release.