Authentication for Single Page Web Applications
Less than to read
In order to obtain a Bearer Token from a SPA application, you need to implement the Authorization Code OAuth 2.0 grant flow using Proof Key for Code Exchange (PKCE). The Proof Key for Code Exchange (PKCE) (defined in RFC 7636) is a technique used to mitigate attacks to the Authorization Code OAuth 2.0 grant flow.
With PKCE, the application creates, for every authorization request, a cryptographically random key called code_verifier
and its transformed value called code_challenge
, which is sent to Sage ID to get the authorization_code
. When the application receives the authorization_code
, it will send the code and the code_verifier
to Sage ID token endpoint to exchange them for the requested tokens.
This OAuth 2.0 flow follows these steps:
- Your SPA application initiates the flow and redirects the user to Sage ID (specifically to the
/authorize
endpoint), sending thecode_challenge
andcode_challenge_method
parameters. - Sage ID redirects the user to your SPA application with an
authorization_code
in the querystring. - Your SPA application sends the
authorization_code
andcode_verifier
together with theredirect_uri
and theclient_id
to Sage ID. This is done using the/oauth/token
endpoint. - Sage ID validates this information and returns an Access Token (and optionally a Refresh Token) to your SPA application.
- Then, your SPA application can use the Access Token to call the Sage 200 API on behalf of the user.
How to Implement it
In the next points, we will work through the steps needed in order to implement it: create a code verifier and a code challenge, get the user’s authorization, get a token and access the Sage 200 API using the token.
1. Create a Code Verifier
First, you need to generate and store a code_verifier
.
function base64URLEncode(str) {
return str.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
var verifier = base64URLEncode(crypto.randomBytes(32));
2. Create a Code Challenge
Using the code_verifier, generate a code_challenge that will be sent in the authorization request.
You must hash this value and not send a plain-text string.
function sha256(buffer) {
return crypto.createHash('sha256').update(buffer).digest();
}
var challenge = base64URLEncode(sha256(verifier));
3. Get the User’s Authorization
To begin an Authorization Code flow, your ASP application should first send the user to the authorization URL, including the code_challenge
and the method used to generate it:
<a href="https://id.sage.com/authorize?audience=861692d/sage200nc.sage.com/api&scope=openid email profile Sales:ReadWrite offline_access&response_type=code&client_id=YOUR_CLIENT_ID&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&redirect_uri=https://id.sage.com/mobile">
Sign In
</a>
4. Exchange the Authorization Code for an Access Token
Now that you have an Authorization Code, you must exchange it for an Access Token that can be used to call the Sage 200 API. Using the Authorization Code from the previous step, you will need to POST to the Token URL sending also the code_verifier
:
var client = new RestClient("https://id.sage.com/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"grant_type\":\"authorization_code\",\"client_id\": \"YOUR_CLIENT_ID\",\"code_verifier\": \"YOUR_GENERATED_CODE_VERIFIER\",\"code\": \"YOUR_AUTHORIZATION_CODE\",\"redirect_uri\": \"https://id.sage.com/mobile\"}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
The response contains access_token
, refresh_token
, id_token
, and token_type
values, for example:
{
"access_token": "eyJz93a...k4laUWw",
"refresh_token": "GEbRxBN...edjnXbL",
"id_token": "eyJ0XAi...4faeEoQ",
"token_type": "Bearer"
}
Note that refresh_token will only be present in the response if you included the offline_access scope.
5. Call the Sage 200 API
Once you have the access_token
you can use it to make calls to the Sage 200 API, by passing it as a Bearer Token in the Authorization header of the HTTP request:
// Use the Access Token to make Sage 200 API calls
$('#get-appointments').click(function(e) {
e.preventDefault();
$.ajax({
cache: false,
url: "https://sage200.sage.es/api/sales/products",
headers: { "Authorization": "Bearer " + access_token }
});
});
6. Get a new Access Token
If you have a refresh_token
, you can call the /oauth/token
endpoint using the refresh_token
grant type, and the refresh token string, to obtain a new Access Token.
var client = new RestClient("https://id.sage.com/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "x-www-form-urlencoded");
request.AddParameter("application/json", "grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=YOUR_CLIENT_ID", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
The response contains access_token
, expires_in
, id_token
, and token_type
values.