Getting started with our Authentication REST API and Norwegian Bank ID
Say you have a business dedicated to matching Norwegian borrowers and lenders. You want to make sure that both parties are who are they say they are, and you decide on verifying this using the Norwegian Bank ID. This could be either in the onboarding flow, or later on to authenticate already registered users.
This is how you would do it using Signicats Rest API.
In this example we will be using Typescript along with the Signicat Rest API. If you prefer to use Open ID Connect, you can check out the guide for that here.
Prerequisites:
Step 1: Sign up for the dashboard at Signicat Dashboard
Step 2: Create an organisation
Step 3: Create a new sandbox account (take note of your account ID)
You find the ID under the account name after creation
Step 4: Add a domain
Step 5: Create a new API-Client (take note of your client ID)
The client ID is found under the client name after creation
Step 6: Add a secret to your API-Client (take note of your client secret)
Step 7: Under “permissions”, enable “Authentication REST API”
Step 8: Enable Norwegian Bank ID
Click add new, and select Norwegian Bank ID. Afterwards it should look like this.
Now you’re all done in the dashboard and can move on to the actual code.
Server Application:
For our server application we will use Node.js with Typescript and Express. If you are unfamiliar with this or want to read more about it you can find more info here: https://nodejs.dev/en/learn/no....
We want our users to verify their identity in order to increase trust on our hypothetical borrower/lender platform. We can use the Signicat REST API for this.
For our example you need two files, index.ts and signicatClient.ts. You will also need to install some dependencies:
npm install axios dotenv express nodemon ts-node typescript @types/axios @types/express
Here is a starting point for you to use our REST API. It’s a basic client for authenticating yourself with our API, along with methods for letting your end users authenticate themselves.
// signicatClient.ts
import axios from "axios";
export class signicatClient {
private clientId: string;
private clientSecret: string;
private accountId: string;
private baseUrl = "https://api.signicat.com";
private token_endpoint = "/auth/open/connect/token";
private session_endpoint = "/auth/rest/sessions";
constructor(clientId: string, clientSecret: string, accountId: string) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accountId = accountId;
}
private getBearerToken(): Promise<string | false> {
// Requests Signicat's servers for a Bearer token to use later
// Signicat expects a base64 encoded string composed of our Client ID and Client Secret as authentication
const basicAuth = Buffer.from(
`${this.clientId}:${this.clientSecret}`,
"binary"
).toString("base64");
// Make post request using Axios
const res = axios
.post(
`${this.baseUrl}${this.token_endpoint}`,
new URLSearchParams({
grant_type: "client_credentials",
scope: "signicat-api",
}),
{
headers: {
Authorization: `Basic ${basicAuth}`,
},
}
)
.then((tokenResponse) => {
// Bearer token is on the 'access_token' property
// expires_in can be changed in the dashboard
console.log(tokenResponse.data);
const bearerToken = tokenResponse.data.access_token;
if (!bearerToken || typeof bearerToken !== "string") {
console.log("Error while getting Bearer token");
return false;
}
// Can now use the token for further requests to the API
return bearerToken;
})
.catch((error): false => {
console.log(error);
console.log("Error while getting Bearer token");
return false;
});
return res;
}
async getAuthSession(sessionId: string) {
const bearerToken = await this.getBearerToken();
if (!bearerToken) {
return false;
}
// Get status for one given session ID, server returns 404 if session is expired (deleted)
const res = await axios
.get(`${this.baseUrl}${this.session_endpoint}/${sessionId}`, {
headers: {
Authorization: `Bearer ${bearerToken}`,
},
})
.then((authResponse) => {
// User has a valid authentication session
console.log(authResponse.data);
return authResponse.data;
})
.catch((error) => {
// No authentication session found / other error
console.log(error.response.data);
return false;
});
return res;
}
async initializeAuthSession(body: any) {
// Starts the authentication flow
const bearerToken = await this.getBearerToken();
if (!bearerToken) {
return false;
}
// Create new session, in return we get a url for our user to authenticate
const authResponse = await axios
.post(`${this.baseUrl}${this.session_endpoint}`, body, {
headers: {
Authorization: `Bearer ${bearerToken}`,
},
params: {
"signicat-accountId": this.accountId,
},
})
.then((response) => {
console.log(response.data);
return response.data;
})
.catch((error) => {
console.log(error);
return false;
});
return authResponse;
}
}
Now for our express app. We want:
- A route to initialialize our end users' authentication.
- Routes to handle successful/aborted/erroneous authentication.
- A function to mark users as verified in our database ( why we’re doing all this ).
That should look something like this:
// index.ts
import express from "express";
import * as dotenv from "dotenv";
import { signicatClient } from "./signicatClient";
dotenv.config();
const app = express();
const port = process.env.port;
const clientId = process.env.clientId ?? "";
const clientSecret = process.env.clientSecret ?? "";
const accountId = process.env.accountId ?? "";
const body = {
prefilledInput: {
nin: "01100844350", // national identification number for testing
},
callbackUrls: {
error: "http://localhost:3000/error",
abort: "http://localhost:3000/abort",
success: "http://localhost:3000/success",
},
flow: "redirect",
requestedAttributes: ["firstName", "lastName", "dateOfBirth"], // properties you want
allowedProviders: ["nbid"],
};
const client = new signicatClient(clientId, clientSecret, accountId);
function updateUserInfoAndMarkAsVerified(userInfo: any) {
console.log("Updating userinfo in database: ");
console.log(userInfo);
console.log("Marking as verified");
console.log("Done!");
}
app.get("/", async (req, res) => {
res.send("Home");
});
app.get("/startauth", async (req, res) => {
// to be filled in
});
app.get("/success", async (req, res) => {
// to be filled in
});
app.get("/abort", async (req, res) => {
res.send("abort");
});
app.get("/error", async (req, res) => {
res.send("error");
});
app.listen(port, async () => {
console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
console.log(`️Verify yourself at at http://localhost:${port}/startauth`);
});
You can store the client ID and secret however you best see fit, I am using a .env file for this example.
// .env
accountId=a-aaaa-aaaaaaaaaaaaaaaa
clientId=aaaaaaa-aaaaaa-aaaaaa-111
clientSecret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
port=3000
There are only really two routes that we need to fill in. One for starting the authentication session, and another for handling a successfull verification. Lets start with the start authentication route:
app.get("/startauth", async (req, res) => {
const authResponse = await client.initializeAuthSession(body); // Start authentication
if (!authResponse) {
res.status(500).send("Internal server error"); // either no bearer token or signicat threw error
}
res.redirect(authResponse.authenticationUrl);
});
The body object that is passed to the signicatClient looks like this and will change depending on your needs. For a fully detailed description of what we expect to recieve along with what you can expect from us, see the documentation for the Authentication API here: Authentication API | Developer Pages
const body = {
prefilledInput: {
nin: "01100844350", // national identification number for testing
},
callbackUrls: {
error: "http://localhost:3000/error",
abort: "http://localhost:3000/abort",
success: "http://localhost:3000/success",
},
flow: "redirect",
requestedAttributes: ["firstName", "lastName", "dateOfBirth"],
allowedProviders: ["nbid"],
};
Now let us create the success route. We check if we get a sessionId, then try to get the session from Signicat. If the session is a successful verification, we store the data in our database.
app.get("/success", async (req, res) => {
const sessionId = req.query.sessionId;
if (!sessionId || typeof sessionId !== "string") {
res.status(400).send("No SessionId in request");
return;
}
const authResponse = await client.getAuthSession(sessionId);
if (!authResponse) {
res.status(400).send("Could not validate session");
}
if (authResponse.status === "SUCCESS") {
updateUserInfoAndMarkAsVerified(authResponse.subject);
}
res.redirect("/");
});
You should now be able to run the app and log in using our test credentials (see below):
nodemon index.ts
NOTE: For testing you can use our Sign-in test credentials:
Choose Norwegian BankID and sign with a test user as follows:
National identity number: 01100844350
One time password: otp
Password: qwer1234
Then, after verifying we can see that the updateUserInfoAndMarkAsVerified function is called :
Wrapping up
You now have validated user info to use in your application. Whether you use this to create a checkmark next to profile pictures, or require it for your own sake, you can now do so. You’d want to fill in the “/error“ and “/abort“ routes and handle them how you see fit. Next you’d want to change the prefilledInput on the body object with the National Identification Number of the person identifying if you already have it from your frontend, or omit it entirely.
Finally, you also need to implement the updateUserInfoAndMarkAsVerified function to actually store the data coming in.
Happy coding!