Add authentication to your Express app with Asgardeo
Tags: express, javascript, asgardeo, oauth20
Published: 2022-04-22 04:09:13

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](https://asgardeo.io/) by [WSO2](https://wso2.com/) comes into play.

#### TLDR;
Try the official sample app [here](https://github.com/asgardeo/asgardeo-auth-node-sdk/tree/main/samples).

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](https://wso2.com/asgardeo/docs/guides/applications/register-standard-based-app/))  

**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.
```curl
yarn add @asgardeo/auth-node cookie-parser express uuid
``` 

To live reload the changes as we do, we need `nodemon` as a dev dependancy. 
```curl
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)
```json
"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.
```Javascript
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
```curl
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. 
```javascript
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](https://console.asgardeo.io/). 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](https://www.npmjs.com/package/uuid) package as well.)
```javascript
const { AsgardeoNodeClient } = require('@asgardeo/auth-node');
const { v4: uuidv4 } = require("uuid");
const config = require('./config');
```
Then initialize the AsgardeoNodeClient with the configuration.
```javascript
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.
```javascript
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.

```javascript
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. 

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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. 

```javascript
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.

```javascript
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.

```javascript
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);
        });
    }
});
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oe8tduszii0krnklvqtw.jpeg)

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](https://github.com/asgardeo/asgardeo-auth-node-sdk/tree/main/samples) 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!