Developing a web or native App with OAuth2 and PKCE
Single page web applications or native apps cannot keep their client secrets private and are therefor called public clients. For such cases the client secret can be omitted by relying on PKCE.
For this guide we assume that you have read the general OAuth2 introduction as well as created an application as shown in the getting started guide.
You may find the whole source code project in this Github repository.
Preparing your ilert app
Before we can start, we need to make sure that our ilert application is configured correctly. Navigate to your application and make sure that you have entered a redirect url that fits your local or deployed webapp. Also copy the client id, as we will need it to configure your application.

Redirecting the user
Using the parameters generated by ilert during the creation of our app, we can build a redirect url for our application, that will take unauthorized users to ilert and ask them to login and grant their permission for our app. In the case of our app, we ask for the profile scope (find more information on other available scopes here), as well as the source:w scope which will grant us permission to read the user's profile data, as well as to read and edit alert sources on behalf of the authorizing user.
Also note that per default for PKCE-only clients no refresh token will be issued, unless the additional offline access scope is requested. Please keep refresh tokens secure.
PKCE generation
Before redirecting the user to the ilert authorization server, we need to generate a verifier and code challenge that can be used by the authorization server to ensure the origin of the (later) token request matches the origin of the authorization request.
Below are helper methods to deal with PKCE generation:
function dec2hex(dec) {
return ("0" + dec.toString(16)).substr(-2);
}
function generateCodeVerifier() {
const array = new Uint32Array(56 / 2);
window.crypto.getRandomValues(array);
return Array.from(array, dec2hex).join("");
}
function sha256(plain) {
const encoder = new TextEncoder();
const data = encoder.encode(plain);
return window.crypto.subtle.digest("SHA-256", data);
}
function base64urlencode(a) {
let str = "";
const bytes = new Uint8Array(a);
const len = bytes.byteLength;
for (var i = 0; i < len; i++) {
str += String.fromCharCode(bytes[i]);
}
return btoa(str)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
async function generateCodeChallengeFromVerifier(v) {
const hashed = await sha256(v);
const base64encoded = base64urlencode(hashed);
return base64encoded;
}
Which may be used like so:
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallengeFromVerifier(verifier);
sessionStorage.setItem("verifier", verifier);
With our verifier and code challenge in place, we can create our redirect url:
const url = "https://app.ilert.com/api/developers/oauth2/authorize";
const queryParams = new URLSearchParams({
client_id: '0501439fd7dee9af0c25',
response_type: 'code',
redirect_uri: 'http://localhost:4597/webapp',
scope: 'profile source:w offline_access',
state: 'optional-state',
code_challenge: '6K9LHF25tUPodHISsO_UoK6cKnbclW2ZY1TaSUAWaIo',
code_challenge_method: 'S256'
});
window.location = url + "?" + queryParams.toString();
In case the user is known to your application or especially if the application is limited to a single account. We suggest to redirect the user directly to the tenant (account url) of the user, this way he might be able to skip the login step if logged in already. e.g. https://someTenant.ilert.com/api/developers/oauth2/authorize
instead of https://app.ilert.com/api/developers/...
User grant view
The user will be able to see the scopes that you have requested and grant them.

In case of an error
In case of any error, ilert's authorization server will send the user back to the provided redirect_uri while adding an error and error_description query parameter to it.
In case of successful authorization
If the user has accepted your scopes, ilert's authorization server will redirect the user back to your provided redirect_uri while adding a code query parameter to it. This code parameter can be used in the next step to fetch the access token.
http://localhost:4597/webapp?code=c82ec4eca4c0ac90061fdd4225d1&state=optional-state
Turning a code into a token
After a successful redirect, we can detect the code query parameter and run an AJAX call to fetch our tokens.
const verifier = sessionStorage.getItem("verifier"); // stored earlier
sessionStorage.removeItem("verifier");
const urlSearchParams = new URLSearchParams(window.location.search);
const code = Object.fromEntries(urlSearchParams.entries()).code;
const tokenParams = new URLSearchParams();
tokenParams.append("client_id", clientId);
tokenParams.append("grant_type", "authorization_code");
tokenParams.append("code", code);
tokenParams.append("code_verifier", verifier);
const tokenUrl = "https://app.ilert.com/api/developers/oauth2/token";
const tokenResult = await axios.post(tokenUrl, tokenParams, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json"
}
});
const {
access_token,
refresh_token
} = tokenResult.data;
console.log(access_token, refresh_token);
In case of an error
You will receive an error response.
{
"code": "invalid_grant",
"error": "Provided code is unknown"
}
In case of successful token generation
If code and secret are valid, the authorization server will respond with an access as well as a refresh token.
Never expose your user's tokens, especially not the refresh tokens, treat them like passwords.
{
"token_type": "Bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpbF9uIjoiNWNmIiwiaWxfYSI6MiwiaWxfcyI6InByb2ZpbGUgc291cmNlOnciLCJpc3MiOiJpbGVydCIsImlsX3UiOiJrcnlzdGlhbml0eSIsImlsX3QiOiJBQ0NFU1MiLCJpbF92IjoxLCJleHAiOjE2NDM3MjY3MjN9.Ao0dTxQpcez0IsaUOdsPzWqWhj31ob9Tzf-5KSoqJEmjC4lkeOiRJ255HI8w2IQyj7aunW_SM50rRQBcDHVkt8JmfuT4TgYJ3dvZiJzeZ-YGU1QDT6pCN30wJ9a3rpmv6Y6T2cJD1JvNuEh1qyjdjmJGuBVU3-_0qZSu-Y6CBaRxaBvM27JOf2JkQ4pAN7dK4c2Yw7-MyEkBhtwI_JEMhNCp6GF5SJGhCFBq-xPF1iFqpllIrpqaTWAvRz0ZCNWZCumXCaJAfvnS_OJJoPwEvr2DBV8x0bAuFpECgycE8_--rjU9gZafqiRd2cfFas7AmzQFuvvDGQx_1Zq7leW3EA",
"expires_in": 3600,
"scope": "profile source:w",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpbGVydCIsImlsX3QiOiJPQVVUSCIsImlsX3YiOjEsImV4cCI6MTY3NTI1OTEyMywiaWxfayI6IjM0ZTMzYzdmZGUzZjQzZTdhMTEwOWJmYTgzMmRkNDdiIn0.ol0HwWO_zAAhKLjLPLDPwvxv7PNPnUnZq8IXd5fC1FY",
"refresh_token_expires_in": 31536000
}
Using the access token
Your application should store the access and especially the refresh token as safely as possible. You can now make requests to ilert's API on behalf of the authorized user.
Do not forget the Bearer prefix in the authorization header before your access token.
var axios = require("axios").default;
var options = {
method: 'GET',
url: 'https://api.ilert.com/api/users/current',
headers: {
Authorization: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpbF9uIjoiNWNmIiwiaWxfYSI6MiwiaWxfcyI6InByb2ZpbGUgc291cmNlOnciLCJpc3MiOiJpbGVydCIsImlsX3UiOiJrcnlzdGlhbml0eSIsImlsX3QiOiJBQ0NFU1MiLCJpbF92IjoxLCJleHAiOjE2NDM3MjczNTR9.ZSxXJiR7yPF9BMfsfa9dYVpdv5E2o88iBTG1VFp7-vdF3wciNte7_fKQU2z2aMVudGIZ6dhpBuA8FnSJa3sjaXgsHy2r8Fjvj2XCTuPigaZgURTjAM0zqTC6bxHVkn-G9bKUoJ8mx40U8hFZFPsnD5b9qiZZ6CjqU0CCuiWjoEC8hapHe1TOYnNvqeFPRUYfOhJTXAuOoW0wmcKdrAyk6YbVN74nDVlEbG1TEGIXk893jVyLaoiShqHxkcnyliTWLYg9P_DG0JXFflxugyGttP3GaxAq28KAxI-5VAa_QptXlwybEVN9Q8072a7Xk4R0_jCKbp9uMKE3E4qH72f3PA'
}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
Refreshing an access token
As access tokens are short lived (read more about token lifetime here), you will have to refresh them regularly, at least in case they expire and the ilert API returns a 401 HTTP status code error.
var axios = require("axios").default;
var options = {
method: 'POST',
url: 'https://api.ilert.com/api/developers/oauth2/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: {
client_id: '0501439fd7dee9af0c25',
refresh_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpbGVydCIsImlsX3QiOiJPQVVUSCIsImlsX3YiOjEsImV4cCI6MTY3NTI1OTEyMywiaWxfayI6IjM0ZTMzYzdmZGUzZjQzZTdhMTEwOWJmYTgzMmRkNDdiIn0.ol0HwWO_zAAhKLjLPLDPwvxv7PNPnUnZq8IXd5fC1FY',
grant_type: 'refresh_token'
}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
The responses will be equal to the ones of Turning a code into a token above.
Accessing token information
You could parse the JWT token yourself, however we offer a token info endpoint for convience. Note that this endpoint only accepts access tokens.
var axios = require("axios").default;
var options = {
method: 'POST',
url: 'https://api.ilert.com/api/developers/oauth2/token_info',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: {
token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpbF9uIjoiNWNmIiwiaWxfYSI6MiwiaWxfcyI6InByb2ZpbGUgc291cmNlOnciLCJpc3MiOiJpbGVydCIsImlsX3UiOiJrcnlzdGlhbml0eSIsImlsX3QiOiJBQ0NFU1MiLCJpbF92IjoxLCJleHAiOjE2NDM3MjczNTR9.ZSxXJiR7yPF9BMfsfa9dYVpdv5E2o88iBTG1VFp7-vdF3wciNte7_fKQU2z2aMVudGIZ6dhpBuA8FnSJa3sjaXgsHy2r8Fjvj2XCTuPigaZgURTjAM0zqTC6bxHVkn-G9bKUoJ8mx40U8hFZFPsnD5b9qiZZ6CjqU0CCuiWjoEC8hapHe1TOYnNvqeFPRUYfOhJTXAuOoW0wmcKdrAyk6YbVN74nDVlEbG1TEGIXk893jVyLaoiShqHxkcnyliTWLYg9P_DG0JXFflxugyGttP3GaxAq28KAxI-5VAa_QptXlwybEVN9Q8072a7Xk4R0_jCKbp9uMKE3E4qH72f3PA'
}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
You will receive an info response like this:
{
"active": true,
"tenant_id": "someTenant",
"username": "someUser",
"scope": "profile source:w",
"app_id": 250
}
Revoking a refresh token
Just like the user may at any time revoke the refresh token that was issued to your application, your application may also proactively revoke the refresh token.
var axios = require("axios").default;
var options = {
method: 'POST',
url: 'https://api.ilert.com/api/developers/oauth2/revoke',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: {
token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpbGVydCIsImlsX3QiOiJPQVVUSCIsImlsX3YiOjEsImV4cCI6MTY3NTI1OTEyMywiaWxfayI6IjM0ZTMzYzdmZGUzZjQzZTdhMTEwOWJmYTgzMmRkNDdiIn0.ol0HwWO_zAAhKLjLPLDPwvxv7PNPnUnZq8IXd5fC1FY'
}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
Make sure to checkout further information on OAuth2 apps.
Last updated
Was this helpful?