Alex
Alex

Reputation: 238

How do I make node.js using express find a JSON Web Token (JWT) inside of a response header cookie in user's browser?

I have a question on how to search for my JWT token inside of a user's browser cookies.

Below I have some code that searches the user's browser for cookies in the response header, but I am not sure how to make the code more specific and search for the JWT token within the cookie and verify that it is an actual JWT token that was a assigned to that user.

const jwt = require('jsonwebtoken');
const router = require('express')();
const cookieParser = require('cookie-parser');
router.use(cookieParser());
module.exports = function(req,res,next){
        const token = req.header('Cookie');
        if (!token) {
            return res.status(403).send('Access Denied');
        }
        try{
            const verified = req.header('Cookie');
            req.user = verified;
            // const verified = jwt.verify(token, process.env.TOKEN_SECRET);
            // req.user = verified;
             next();
        } catch (err) {
            res.clearHeader;
            res.status(403).send('Invalid Token');
        }
    };

Upvotes: 1

Views: 1524

Answers (1)

Someone
Someone

Reputation: 225

I hope I didn't misunderstand your question and waste a bunch time.

Short Answer: How to retrieve information

Use req.body or req.headers. If something will contain the token or authentication details, then it's one of these two.

Full Auth Walkthrough:

To get the JSON Web Tokens you first have to generate them. Wouldn't recommend implementing your own token authentication though. I'll show how to create a whole authentication system here step by step.

For simplicity, let's say we have an exported route in a file auth.js, this route will be a sub-route domain.com/auth, an array of all active refreshTokens and the jwt:

const express = require("express")
const jwt = require("jsonwebtoken")
let route = (exports.route = express())
let refreshTokens = []

What we will do is generate a long-lasting refresh token, which users will be able to use to generate a smaller 15-minute access token. Afterwards, you generate a new access token with the refresh token and so on. But to get the refresh token you need to login or register. Users can also logout killing the refresh token.

route.post("/token", async (req, res) => {
    // Input: Refresh Token
    // Output: Access Token Generation
})

route.post("/login", async (req, res) => {
    // Input: User, Password
    // Output: Refresh Token
})

route.delete("/logout", async (req, res) => {
    // Input: Token to Remove
})

Let's start with the end. You have a refresh token, you won't to destroy it. Simply filter the array against this token and submit a status. The token becomes unusable after it's cleared from the array, that's the goal here.

route.delete("/logout", async (req, res) => {
    refreshTokens = refreshTokens.filter((token) => token != req.body.token)
    res.sendStatus(204)
})

With me so far? Now let's jump back to the start. If you log in with an email and password, if they're wrong respond with an error message, if they're correct receive the tokens.

route.post("/login", async (req, res) => {
    const username = req.body.username
    const password = req.body.password

    // This is just a quick demonstration,
    // you would have to use the bcrypt hash
    // or other hash/salt methods. DO NOT
    // STORE passwords plaintext

    // Not existent user = Unauthorized
    if (username != 'admin') return res.sendStatus(401)
    // Wrong Password = Forbidden
    if (password != 'abc123') return res.sendStatus(403)

    const user = {
        id: 0,
        username: username,
        password: password
    }

    const accessToken = generateAccessToken(user)
    const refreshToken = generateRefreshToken(user)

    let result = {
        success: true,
        accessToken: accessToken,
        refreshToken: refreshToken,
    }

    res.send(result)
})

Now how do we sign the JSON web tokens? Let's take a look at the two methods used here:

function generateAccessToken(content) {
    return jwt.sign(content, process.env.ACCESS_TOKEN_SECRET, {
        expiresIn: "15m",
    })
}

function generateRefreshToken(content) {
    const token = jwt.sign(content, process.env.REFRESH_TOKEN_SECRET)
    refreshTokens.push(token)
    return token
}

Both use some sort of environment tokens, but why? That's the token you will have to generate once for the back end. It will be used as a public key. We simply generate the access tokens for 15 minutes and push the refresh tokens to the array.

route.post("/token", async (req, res) => {
    const refreshToken = req.body.token

    if (refreshToken == null) return res.sendStatus(401)
    if (!refreshTokens.includes(refreshToken)) return res.sendStatus(403)

    jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
        if (err) return res.sendStatus(403)
        res.json({ accessToken:
            generateAccessToken({
                id: 0,
                username: user.name,
                password: user.password
            })
        })
    })
})

We verify the refresh token, if it exists and it is valid, return a new access token for 15 minutes. That's it for the token part, you can login (create refresh token), retrieve an access token and logout (kill refresh token)

How to Use: Authenticate and Authorize

Admin pages should return 403 while the forum board should be different whether you're logging as a guest or an actual user. The first one is authentication, the second authorization.

Let's create two functions for each. Express is quite handy with the next() function

exports.authenticate = function (req, res, next) {
    const authHeader = req.headers["authorization"]
    const token = authHeader?.split(" ")[1]

    jwt.verify(token || "", process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        req.user = err ? {} : user
        next()
    });
};

exports.authorize = function (req, res, next) {
    const authHeader = req.headers["authorization"]
    const token = authHeader?.split(" ")[1]

    if (token == null)
        return res.sendStatus(401)

    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        if (err) return res.sendStatus(403)
        req.user = user
        next()
    })
}

Now you're done with the whole authentication system (aside some cleanup's) and probably the registration system. Let's make use of it.

Client side you can create a REST api like so:

POST http://localhost:8081/auth/login
Content-Type: application/json

{
    "username": "admin",
    "password": "abc123"
}

# Returns refresh and access token.

###

DELETE http://localhost:8081/auth/logout
Content-Type: application/json

{
    "token": "REFRESH_TOKEN"
}

# Logs out a user.

###

POST http://localhost:8081/auth/token
Content-Type: application/json

{
    "token": "REFRESH_TOKEN"
}


#
# This is how you can provide the access token
# when making a request to say a forum api
#

GET http://localhost:8081/forum/api/board/0
Authorization: Bearer ACCESS_TOKEN

Usage:

route.get("forum/board/:id", authenticate, async (req, res) => {
    res.send(req.user)
})

Expected Output when going to localhost:8081/forum/board/7 authenticated:

{id:0,username:"admin",password:"abc123"}

Otherwise:

{}

Nevertheless, do not try implementing your own authentication. Really, you shouldn't.

Source

https://www.youtube.com/watch?v=mbsmsi7l3r4

Upvotes: 2

Related Questions