OneSpan Sign developers: How to secure API calls with OAuth 2.0

Securing API integrations can be challenging, especially when dealing with sensitive eSignature workflows. This API security tutorial demonstrates how to implement robust OAuth 2.0 authentication for OneSpan Sign APIs, eliminating common security vulnerabilities in REST API integrations. You'll master JWT token authentication, learn the client credentials flow, and discover how OneSpan's authorization server enables secure bearer token authentication without exposing user credentials. Whether you're a developer new to OAuth or looking to enhance your existing OneSpan Sign integration, this guide provides practical code examples and API security best practices you can implement immediately.
Introduction to OAuth 2.0 for developers
OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service, such as APIs provided by Google, Microsoft, or OneSpan Sign. It’s widely adopted for securing APIs and delegating user access in web, desktop, and mobile applications.
Unlike traditional authentication, OAuth 2.0 allows users to grant access to their data without sharing their credentials, using access tokens issued by a trusted authorization server.
Key roles in OAuth 2.0
Before diving into the flow, it’s important to understand the four main actors:
Role | Description |
Resource owner | The user who owns the data. |
Client | The app requesting access to the user's data. |
Authorization server | Issues tokens to the client after authenticating the resource owner. |
Resource server | The API or service that holds the user's data. |
OAuth 2.0 authorization code flow
Let’s look at an example flow with OneSpan Sign:

Figure 1: Summary of OAuth Flow in OneSpan Sign
The diagram in Figure 1 illustrates a server-to-server OAuth 2.0 flow using client credentials with JWT access tokens, specifically in the context of OneSpan Sign and an external authorization server for OneSpan Sign.
Here is a step-by-step breakdown of the OAuth flow shown in the diagram:
- 1. The application client (e.g., a backend service) initiates authentication by sending its client ID and client secret to the OneSpan Sign authorization server.
- 2. The OneSpan Sign authorization server validates the client credentials.
- 3. The application client includes the JWT access token in the authorization header (as bearer <token>) when calling OneSpan Sign REST API.
- 4. When the OneSpan Sign API receives the request, it forwards the token to the OneSpan Sign authorization server (or a JWT validation service).
- 5. The authorization server confirms whether the token is valid or not, and returns the result to OneSpan Sign.
- 6. The OneSpan Sign API processes the request and returns a response to the application client.
Why Use OAuth 2.0?
- Security: Users never share credentials with third-party apps.
- Scalability: Decouples authentication from resource access.
- Token-based: Enables fine-grained access control, token expiration, and revocation.
- Standardized: Works across different identity providers and APIs.
Using cURL
First, you need to get your OAuth credentials from your OneSpan Sign dashboard (i.e. client-id and client-secret). You will need to login as the administrator of the account and go to Admin > API Access.
In the Authentication Settings, select the OAuth 2.0 option:

Figure 2: Authentication Settings in the OneSpan Sign dashboard
Next, click on the OAuth 2.0 tab on the left-hand side. In here, you generate your client ID and client secret. Note the authorization server URL.

Figure 3: OAuth 2.0 in the OneSpan Sign dashboard
Note that it can take up to 5 minutes for your OAuth client to be active.
You’re now ready to make your first API call using OAuth. To retrieve an access token:
cURL -H 'Content-Type: application/x-www-form-urlencoded' \
-u "clientId:clientSecret" \
https://sandbox.esignlive.com/oauth2/token \
-d "grant_type=client_credentials"
Parameter | Condition | Description |
Authorization | Required | Client credentials are accepted in the form of Basic Auth pattern or a header parameter as per RFC 6749. In the request headers, pass the Base64 encoded string representing your 'client_id' and 'client_secret' values, appended to the text Basic as follows: ``` Basic <Base64 encoded client_id and client_secret> ``` Base64 encoded client_id and client_secret = Base64 of "client_id:client_secret" |
grant_type | Required | Must be set to client_credentials. Starting from release 24.R5, 'grant_type' will not be accepted as a query parameter. It will only be accepted as a form data. |
Successful response
A successful response looks like this:
{
"access_token": "eyJraWQiOiIxOTk5OGJhOS1jYTY1LTQ3ODYtOGYzMi01ZGUxZDNhM2JhYTUiL....",
"token_type": "Bearer",
"expires_in": 299
}
Parameter | Description |
access_token | The requested access token. The app can use this token to authenticate to the secured resource, such as a web API. |
token_type | Indicates the token type value. The only type that the OneSpan Sign API supports is bearer. |
expires_in | The amount of time that an access token is valid (in seconds). |
Note: The OneSpan Sign API does not offer token refresh functionality, so a new token must be generated once the existing one expires. This requires additional handling on the application side by integrators using the OneSpan Sign API. On the other hand, the Java and .NET SDKs provides automatic token recreation on the client side, simplifying the process.
Now, grab the access token and use it to make a call to the APIs:
cURL -X POST --location 'http://sandbox.e-signlive.ca/api/packages' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer yJraWQiOiIxOTk5OGJhOS1jYTY1LTQ3ODYtOGYzMi01ZGUxZDNhM2JhYTUiLCJhbGciOiJSUzI1
NiJ9.eyJzdWIiOiJsWWZSMlluWThRaUVzaU1OT3Z5YmxuTWVMIiw
iYXVkIjoibFlmUjJZblk4UWlFc2lNTk92eWJsbk1lTCIs
Im5iZiI6MTcwNjI3MzI1MywiaXNzIjoiaHR0cDovL2lud
GVybmFsLW9zcy1hdXRob3JpemF0aW9uLXN
jc2lrYS0xMzE0NjM3MzA3LnVzLWVhc
3QtMS5lbGIuYW1hem9uYXdzLmNvbSIsImV4c
CI6MTcwNjI3MzU1MywiaWF0IjoxNzA2MjczMjUzLCJqdGkiOiJlNDZiYTVi
Mi0zZmQzLTQzYjktOGQ0OS00Njg0NGEyYmQ1ZDIifQ.lGTWVxirs7G8nfXQC-enOpMUCdbmDp1hWnEHbLSuOKknPPrr0_Ewk
SMG2HHBGbMuDy_eY_jttLcdGI_PA10cikzCN0DSTnAmkAn
7Av24BbqthTNqJsQfeCY0y8G14hnjmuDC7-yIsNO55X6YFtoF4MBxc7Z49PQUsjYmA9cxh0xgc
MVLqRMbfHKdgIqSRLBtHdh4jdNn_xaoE-vCPCSnR2ocsacZEb-u0NX0f7Nt3f0l8g0JH2BR99RHUkUdc7T47rqda_wnhBB1fTTH
t2p8mBkDbjMlaTkTAGX1U9tq0dBAePEDNw0DkDUVzGVNc2XafnBniiMZK0PvS4aN5PEkdw' \
--data '{
"name": "Dummy transaction", \
"description": "dummy transaction creation using OAuth", \
"type": "PACKAGE", \
"language": "en", \
"autocomplete": true, \
"sender": { \
"email": "[email protected]"\
}, \
"roles": [ \
{ \
"id": "client", \
"type": "SIGNER", \
"index": 1, \
"signers": [ \
{ \
"firstName": "Someone", \
"lastName": "Signer", \
"email": "[email protected]" \
} \
], \
"name": "signer" \
}\
}, \
"name": "Sample Contract" \
}] \
}'
Using Java SDK
When using the Java or .NET SDK, the generated token is used until it expires instead of creating a new token on every call.
The SDK handles this properly, by caching the clients created. Those clients retain the token and before every call made by the SDK, the token expiry is verified and the SDK will decide on whether a new token is needed or not.
Hence, when using the SDK, there's no need to implement a check on the access token expiry since this is handled automatically.
EslOAuthClientConfig config = new EslOAuthClientConfig.Builder()
.withClientId("clientId") // Mandatory: replace with your clientId
.withClientSecret("clientSecret") // Mandatory: replace with your clientSecret
.withAuthenticationServer("authenticationServer") // Mandatory: replace with the OneSpan's authorization server url
.withApiUrl("apiUrl") // Mandatory: replace with OneSpan's API url
.withAllowAllSSLCertificatesFlag(allowAllSSLCertificatesFlag) // Optional: default false
.withUseSystemProperties(useSystemProperties) // Optional: default false. If this is set to true then all proxy configurations should be passed as system properties
.withProxyConfig(proxyConfig) // Optional: default null. To be passed if proxy is required and useSystemProperties is set to false
.withHeaders(headers) // Optional: default empty map. To be passed if extra headers are need to the request
.build();
EslClient eslClient = EslOAuthClientProvider.getInstance().getEslClient(config);
//proceed to make calls to our API using eslClient.
Using .NET SDK
Similarly to the Java SDK:
OSSAuthClientConfig config = new OSSAuthClientConfig.Builder()
.WithClientId(clientId) // Mandatory: replace with your clientId
.WithClientSecret(clientSecret) // Mandatory: replace with your clientSecret
.WithAuthenticationServer(authenticationServer)// Mandatory: replace with the OneSpan's authorization server url
.WithApiUrl(apiUrl) // Mandatory: replace with OneSpan's API url
.WithAllowAllSSLCertificatesFlag(allowAllSSLCertificatesFlag) // Optional: default false
.WithUseSystemProperties(useSystemProperties) // Optional: default false. If this is set to true then all proxy configurations should be passed as system properties
.WithProxyConfiguration(proxyConfig) // Optional: default null. To be passed if proxy is required and useSystemProperties is set to false
.WithHeaders(headers) // Optional: default empty map. To be passed if extra headers are need to the request
.Build();
OssClient ossClient = OSSAuthClientProvider.Instance.GetOssClient(config);
//proceed to make calls to our API using ossClient
In both cases above the ossClient / eslClient will be cached in memory and the SDKs will enforce that the clients reuse the oAuth2 token and regenerate a new token when it expires.