When you are writing code for a project, at some point you're gonna have to come across user logins. To perform user specific tasks, to save user data and to do various kinds of stuff, user accounts is a mandatory feature in any system. But building a fully fledged login system is not so easy: especially having the flexibility to incorporate many kinds of login options (Email and password, Social Logins, Magic Links, etc) securely takes in a lot of effort. That is where Asgardeo by WSO2 comes into play.
TLDR;
Try the official sample app here.
Asgardeo is an IDaaS (Identity as a Service) empowering developers to implement secure and frictionless access. In a broader sense, it is a cloud-based solution that offers Identity and Access Management (IAM) capabilities to your applications easily. Now let's have a look at how we can integrate Asgardeo OIDC (Open ID Connect) authentication to our Express application. For that, we will be using Asgardeo Node SDK.
Prerequisites
- An Asgardeo organization. (You can create an organization upon registration at https://asgardeo.io/signup).
- A Standard Based Application - Once you create an organization in the Asgardeo Console, you can create a Standard Based Application from the dashboard. (More info)
Projet Initialization
Let's get started. First and foremost you're gonna have to create a new folder (or a directory) and initialize a new project. You can do that with yarn init --y
and it will create a new package.json
file for you. Now open your folder in your favourite code editor and we're ready to proceed.
Now you have to install all the dependancies.
yarn add @asgardeo/auth-node cookie-parser express uuid
To live reload the changes as we do, we need nodemon
as a dev dependancy.
yarn add -D nodemon
After installing, add the following to your package.json
file. (If you already have a scripts block, you can replace it)
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "nodemon server.js"
}
Implement an Express app
Create a new file and name it as server.js
. (You can choose whatever the name you want :p but make sure you change the name in the package.json
as well!)
Then create a simple express server inside your server.js
file. Something similar to this.
const express = require('express');
const cookieParser = require('cookie-parser');
//Define the Port
const PORT = 5000;
//Initialize Express App
const app = express();
app.use(cookieParser());
app.get("/", (req, res) => {
res.send("Hello World");
});
//Start the app and listen on the PORT
app.listen(PORT, () => { console.log(`Server Started at PORT ${ PORT }`); });
Run the server with
yarn run serve
Now if you navigate to localhost:5000
, you should see a "Hello World".
Integrate Asgardeo Authentication
Now that we have a working Express app, let's integrate Asgardeo authentication. First you need to create a new file and name it to config.js
.
Inside that file, you need to specify the configuration for Asgardeo initialization.
const config = {
"clientID": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET",
"baseUrl": "YOUR_BASE_URL",
"signInRedirectURL": "http://localhost:5000/login",
"signOutRedirectURL": "http://localhost:5000",
"scope": [ "openid", "profile" ],
};
module.exports = config;
Replace the clientID
, clientSecret
and baseUrl
from the values you get from Asgardeo Console. The signInRedirectURL
is the one a user should be redirected after the login. Keep in mind that the login endpoint and signInRedirectURL
should be the same (You'll see why later).
Also make sure you add localhost:5000
as an allowed origin and add signInRedirectURL
as an authorized redirect URL in the console.
Now open the server.js
file and import the Asgardeo Node SDK and our config file. (Note: As an additional package, we need the uuid package as well.)
const { AsgardeoNodeClient } = require('@asgardeo/auth-node');
const { v4: uuidv4 } = require("uuid");
const config = require('./config');
Then initialize the AsgardeoNodeClient with the configuration.
const authClient = new AsgardeoNodeClient(config);
After that, we can move onto implementing the login endpoint.
Earlier, we declared our signInRedirectURL
in the config file to a /login
endpoint. So we'll implement that endpoint here.
app.get("/login", (req,res) => {
});
Inside the route handler, first you need to check if the incoming request is already authenticated or not. To do this, we can check the ASGARDEO_SESSION_ID
cookie. If the request has a session ID, we can use that to re-authenticate the user and if not, we can create a new session ID and assign it to the request.
let userID = req.cookies.ASGARDEO_SESSION_ID;
if (!userID) {
userID = uuidv4();
}
After that, you have to implement a callback function which will be used to redirect the user to the authorization URL. At the same time, you can send the user ID as a cookie back to the user. Make sure you use the httpOnly and sameSite attributes to prevent your cookie from cross-site request forgery (CSRF) attacks.
const redirectCallback = (url) => {
if (url) {
//Make sure you use the httpOnly and sameSite attributes to prevent from cross-site request forgery (CSRF) attacks.
res.cookie('ASGARDEO_SESSION_ID', userID, { maxAge: 900000, httpOnly: true, sameSite:'lax' });
res.redirect(url);
return;
}
};
This function will take in the authorization URL string as a parameter and will redirect the user. Now we will use the signIn
function from the SDK to authorize the user.
authClient.signIn(redirectCallback, userID, req.query.code, req.query.session_state, req.query.state).then(response => {
//Just to see whats going on
console.log(response);
if (response.idToken) {
res.status(200).send(response);
}
});
Now let's figure out why we added the signInRedirectURL
in the config file as same as the login endpoint (/login
). The signIn
method accepts 5 parameters namely redirectCallback, userID, code, session_state and state. When a user logs in for the first time, the /login
endpoint will not get any values for code
, session_state
and state
even though the signIn
function expects the URL to have them. If those parameters are not present, the SDK will generate an authorization URL and passes it down to the redirectCallback
function. From there, the user will be redirected to the Asgardeo Login Page and will securely authenticated. After doing so, as per our config file, the user will be redirected into the signInRedirectURL
. This redirection will have the code
, session_state
and state
URL parameters. Now the SDK knows this user has been redirected from the Asgardeo Login and will validate the code
, session_state
and state
, then create a session in the Express app against the userID
. There is a complicated process under the hood, but the user just have to call /login
endpoint once and the SDK will handle the flow smoothly.
The final code block for the /login
endpoint will look like this.
app.get("/login", (req, res) => {
let userID = req.cookies.ASGARDEO_SESSION_ID;
if (!userID) {
userID = uuidv4();
}
const redirectCallback = (url) => {
if (url) {
//Make sure you use the httpOnly and sameSite attributes to prevent from cross-site request forgery (CSRF) attacks.
res.cookie('ASGARDEO_SESSION_ID', userID, { maxAge: 900000, httpOnly: true, sameSite:'lax' });
res.redirect(url);
return;
}
};
authClient.signIn(redirectCallback, userID, req.query.code, req.query.session_state, req.query.state).then(response => {
console.log(response);
if (response.idToken) {
res.status(200).send(response);
}
});
});
Now you have a working login endpoint for your users 🥳. Let's add a log out endpoint as well.
Logging out is rather easy than implementing the log in. We'll start with defining a new route.
app.get("/logout", (req,res) => {
});
In the logout logic, first we need to check if the session ID cookie is present in the request. If so, we know this user has already been authenticated.
if (req.cookies.ASGARDEO_SESSION_ID === undefined) {
res.send("Unauthenticated");
}
If the cookie is not present, we reject the logout request. But if it's present, we need to logout the user and delete the session.
authClient.signOut(req.cookies.ASGARDEO_SESSION_ID).then(response => {
//Invalidate the session cookie
res.cookie('ASGARDEO_SESSION_ID', null, { maxAge: 0 });
res.redirect(response);
}).catch(err => {
console.log(err);
res.send(err);
});
We can extract the session ID from the cookie and then pass it on to signOut
function. It will identify the relevant user's session and will destroy accordingly. After that, we need to invalidate the cookie. This can be done by setting the value to null
sending back. So the code block for /logout
endpoint will look like this.
app.get("/logout", (req, res) => {
if (req.cookies.ASGARDEO_SESSION_ID === undefined) {
res.send("Unauthenticated");
} else {
authClient.signOut(req.cookies.ASGARDEO_SESSION_ID).then(response => {
//Invalidate the session cookie
res.cookie('ASGARDEO_SESSION_ID', null, { maxAge: 0 });
res.redirect(response);
}).catch(err => {
console.log(err);
res.send(err);
});
}
});
Congratulations now you have implemented @asgardeo authentication to your Express app. The Asgardeo Node SDK comes up with more functions that will be useful to implement your next project so do check them out as well. Please check the official sample app here and you can try out the SDK including the functions we coded earlier in this article. That's it for now, thanks for reading. See you folks soon!
Top comments (0)