DEV Community

Cover image for Add authentication to your Express app with Asgardeo
Suvin Nimnaka
Suvin Nimnaka

Posted on • Edited on

Add authentication to your Express app with Asgardeo

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
Enter fullscreen mode Exit fullscreen mode

To live reload the changes as we do, we need nodemon as a dev dependancy.

yarn add -D nodemon
Enter fullscreen mode Exit fullscreen mode

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"
  }
Enter fullscreen mode Exit fullscreen mode

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 }`); });
Enter fullscreen mode Exit fullscreen mode

Run the server with

yarn run serve
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

Then initialize the AsgardeoNodeClient with the configuration.

const authClient = new AsgardeoNodeClient(config);
Enter fullscreen mode Exit fullscreen mode

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) => {

});
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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;
       }
  };
Enter fullscreen mode Exit fullscreen mode

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);
    }
});
Enter fullscreen mode Exit fullscreen mode

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);
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

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) => {

});
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
        });
    }
});
Enter fullscreen mode Exit fullscreen mode

Image description

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)