Java Web Tokens: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
(6 intermediate revisions by the same user not shown)
Line 28: Line 28:
=Refresh Tokens=
=Refresh Tokens=
When the use authenticates they are provided with an access token. The user can then request a new access token with a refresh token. The access token typically has a much shorter lifespan.
When the use authenticates they are provided with an access token. The user can then request a new access token with a refresh token. The access token typically has a much shorter lifespan.
[[File:Jwt Refresh.png|200px]]
[[File:Jwt Refresh.png|200px]]<br>
The key question when working on this was where to store the refresh token. After all it enables you to get an access token. In the NodeJS example I use HttpOnly cookie which seemed to be ok. https://owasp.org/www-community/HttpOnly
=NodeJS Example=
This is not production code and should not be used as such
==End Points==
===Login===
Here is the code for logging in. The use is stored in an array. The access token and refresh token are created.
<syntaxhighlight lang="js">
// Login user
server.post('/login', async (req,res) => {
 
    const {email, password} = req.body
 
    try {
 
        // Check if user exists
        const user = fakeDB.find(user => user.email === email)
        if(!user) throw new Error('User does not exists')
 
        const valid = await compare(password, user.password)
        if(!valid) throw new Error('Password or user incorrect')
 
        // Create Access and Refresh token
        const accessToken = createAccessToken(user.id)
        const refreshAccessToken = createRefreshAccessToken(user.id)
 
        // Puts the refresh access token in database
        user.refreshAccessToken = refreshAccessToken
        console.log(fakeDB)
 
        // Sends refresh access token as a cookie
        // Sends access token in header
        sendRefreshAccessToken(res,refreshAccessToken)
        sendAccessToken(req,res,accessToken)
 
    }
    catch(exp) {
        res.send({error: exp.message})
    }
})
</syntaxhighlight>
===Logout===
The cookie is cleared
<syntaxhighlight lang="js">
server.post('/logout', async (req,res) => {
    res.clearCookie('refreshtoken',{ path: '/refresh_token'})
    return res.send(
        {
            'message': 'User has logged out',
        })
})
</syntaxhighlight>
===Refresh===
Create a new access token using the refresh token
<syntaxhighlight lang="js">
server.post('/refresh_token', async (req,res) => {
 
    const token = req.cookies.refreshtoken
 
    if (!token) return res.send({accessToken: ''})
 
    let payload = null
 
    // Check as have a token and it is verified
    try {
        payload = verify(token, process.env.REFRESH_TOKEN_SECRET)
    }
    catch(exp) {
        return res.send({accessToken: ''})
    }
 
    // Extract the user from the payload and check it exists
    const user  = fakeDB.find(user => user.id === payload.userId)
 
    if (!user ||user.refreshAccessToken !== token)
        return res.send({accessToken: ''})
 
    // Token exists, create a new refresh and access token
    const accessToken = createAccessToken(user.id)
    const refreshAccessToken = createRefreshAccessToken(user.id)
    user.refreshAccessToken = refreshAccessToken
 
    // Send to User as cookie and regular response
    sendRefreshAccessToken(res,refreshAccessToken)
    return res.send({accessToken})
})
</syntaxhighlight>
===Protected===
Verify the user is allowed to access endpoint
<syntaxhighlight lang="js">
server.post('/protected', async (req,res) => {
 
    try {
        const userId = isAuth(req)
        if(userId !== null) {
            res.send({
                data: 'This is protected data.',
            })
        }
    }
    catch(exp) {
        res.send({error: exp.message})
    }
})
</syntaxhighlight>
 
==Token Creation==
The nodejs uses jsonwebtoken
<syntaxhighlight lang="js">
const createAccessToken = userId => {
    return sign(
        {userId},
        process.env.ACCESS_TOKEN_SECRET,
        { expiresIn: '15m'})
}
 
const createRefreshAccessToken = userId => {
    return sign(
        {userId},
        process.env.REFRESH_TOKEN_SECRET,
        { expiresIn: '7d'})
}
 
const sendAccessToken = (req, res, accessToken) => {
 
    res.send({
        accessToken,
        email:req.body.email
    })
}
 
const sendRefreshAccessToken = (res, refreshAccessToken) => {
 
    res.cookie('refreshtoken', refreshAccessToken, {
        httpOnly: true,
        path: '/refresh_token',
    })
}
</syntaxhighlight>
==Authorization==
Read the bearer token, must ask the next Swedish person to say this.
<syntaxhighlight lang="js">
const isAuth = req => {
    const authorisation = req.headers['authorization']
    if(!authorisation) throw new Error("User must login")
 
    const token = authorisation.split(' ')[1]
    const {userId} = verify(token, process.env.ACCESS_TOKEN_SECRET)
   
    return userId
}
</syntaxhighlight>

Latest revision as of 23:39, 31 March 2021

Introduction

Java Web Tokens are used for Authorisation and Information Exchange. They consist of three parts, a header, Payload and a Signature. For example

Format

Header

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Example

Refresh Tokens

When the use authenticates they are provided with an access token. The user can then request a new access token with a refresh token. The access token typically has a much shorter lifespan.
The key question when working on this was where to store the refresh token. After all it enables you to get an access token. In the NodeJS example I use HttpOnly cookie which seemed to be ok. https://owasp.org/www-community/HttpOnly

NodeJS Example

This is not production code and should not be used as such

End Points

Login

Here is the code for logging in. The use is stored in an array. The access token and refresh token are created.

// Login user 
server.post('/login', async (req,res) => {

    const {email, password} = req.body

    try {

        // Check if user exists
        const user = fakeDB.find(user => user.email === email) 
        if(!user) throw new Error('User does not exists')

        const valid = await compare(password, user.password)
        if(!valid) throw new Error('Password or user incorrect')

        // Create Access and Refresh token
        const accessToken = createAccessToken(user.id)
        const refreshAccessToken = createRefreshAccessToken(user.id)

        // Puts the refresh access token in database
        user.refreshAccessToken = refreshAccessToken
        console.log(fakeDB)

        // Sends refresh access token as a cookie
        // Sends access token in header
        sendRefreshAccessToken(res,refreshAccessToken)
        sendAccessToken(req,res,accessToken)

    }
    catch(exp) {
        res.send({error: exp.message})
    }
})

Logout

The cookie is cleared

server.post('/logout', async (req,res) => {
    res.clearCookie('refreshtoken',{ path: '/refresh_token'})
    return res.send(
        {
            'message': 'User has logged out',
        })
})

Refresh

Create a new access token using the refresh token

server.post('/refresh_token', async (req,res) => {

    const token = req.cookies.refreshtoken

    if (!token) return res.send({accessToken: ''})

    let payload = null

    // Check as have a token and it is verified
    try {
        payload = verify(token, process.env.REFRESH_TOKEN_SECRET)
    }
    catch(exp) {
        return res.send({accessToken: ''})
    }

    // Extract the user from the payload and check it exists
    const user  = fakeDB.find(user => user.id === payload.userId)

    if (!user ||user.refreshAccessToken !== token)
        return res.send({accessToken: ''})

    // Token exists, create a new refresh and access token
    const accessToken = createAccessToken(user.id)
    const refreshAccessToken = createRefreshAccessToken(user.id)
    user.refreshAccessToken = refreshAccessToken

    // Send to User as cookie and regular response
    sendRefreshAccessToken(res,refreshAccessToken)
    return res.send({accessToken})
})

Protected

Verify the user is allowed to access endpoint

server.post('/protected', async (req,res) => {

    try {
        const userId = isAuth(req)
        if(userId !== null) {
            res.send({
                data: 'This is protected data.',
            })
        }
    }
    catch(exp) {
        res.send({error: exp.message})
    }
})

Token Creation

The nodejs uses jsonwebtoken

const createAccessToken = userId => {
    return sign(
        {userId}, 
        process.env.ACCESS_TOKEN_SECRET,
        { expiresIn: '15m'})
}

const createRefreshAccessToken = userId => {
    return sign(
        {userId}, 
        process.env.REFRESH_TOKEN_SECRET,
        { expiresIn: '7d'})
}

const sendAccessToken = (req, res, accessToken) => {

    res.send({
        accessToken,
        email:req.body.email
    })
}

const sendRefreshAccessToken = (res, refreshAccessToken) => {
   
    res.cookie('refreshtoken', refreshAccessToken, {
        httpOnly: true,
        path: '/refresh_token',
    })
}

Authorization

Read the bearer token, must ask the next Swedish person to say this.

const isAuth = req => {
    const authorisation = req.headers['authorization']
    if(!authorisation) throw new Error("User must login")

    const token = authorisation.split(' ')[1]
    const {userId} = verify(token, process.env.ACCESS_TOKEN_SECRET)
    
    return userId
}