Skip to main content

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.

info

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​

  1. Log in to the Legalesign Developer Portal as an organisation administrator with developer tools enabled
  2. Go to OAuth Clients
  3. Select your organisation
  4. Create a client: give it a name and add your redirect URI(s)
  5. Copy the client ID and the client secret
warning

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
ParameterValue
response_typecode
client_idYour client ID from the developer portal
redirect_uriMust exactly match a registered redirect URI
scopeopenid profile email offline_access. Include offline_access to receive a refresh token
code_challengeBase64url(SHA256(code_verifier)), see PKCE below
code_challenge_methodS256
promptconsent. Needed, together with offline_access, to receive a refresh token
stateA 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 token1 hour
Refresh token30 days since it was last used. Each refresh issues a new one
The user's authorisation12 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:

ClaimDescription
subThe user's identifier
usernameThe user's Legalesign username
org_idThe organisation your client is registered under
client_idYour OAuth client ID
twofactorWhether the user has two-factor authentication set up
scopeThe scopes granted
isshttps://oauth.legalesign.com
audhttps://graphql.uk.legalesign.com
exp, iatExpiry and issue time (Unix timestamps)
jtiUnique 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=consent with offline_access if 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.
What a token can reach

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.