Search code examples
typescriptnestjsargon2

argon2 verify almost always returns true when comparing jwt tokens


I am using nestjs on my server, I am using passport based logic for authenfication and i have problem with my refresh token. I use it only to refresh my access and refresh tokens in refreshTokens method and i hash it using argon2. I had problems with hashing token with bcrypt, because token was too long for bcrypt to hash it correctly and it always returned true when verifying.

async refreshTokens(userId: number, rt: string, res: Response): Promise<AccessToken> {
    const admin = await this.adminService.findAdminById(userId)
    if (!admin || !admin.refreshToken) throw new ForbiddenException('Access denied')

    console.log(rt, 'service method')
    const rtMatches = await argon2.verify(admin.refreshToken, rt)
    if (!rtMatches) throw new ForbiddenException('Access denied')

    const tokens = await this.createTokens(admin.id, admin.userName)
    await this.updateRefreshToken(admin.id, tokens.refresh_token)
    res.cookie('refresh_token', `${tokens.refresh_token}`, {
        httpOnly: true
    })
    return {
        access_token: tokens.access_token
    }
}

In code above my method gets userId(is extracted from refresh token in decorator) to find admin in database, then compare value of refresh token saved in database with rt(that is extracted from cookies and then passed in refreshTokens method), for comparison is used argon2.verify() method.

I am using postman to call endpoints.

So, when I login and get access and refresh tokens I pass refresh token to cookies in postman and call refreshTokens method via postman and method is executed successfully and rtMatches returns true. Then I check my MySQL database and hash of refresh token there changed. And problem arises when I call refreshTokens method again using the first token, that was rewritten by new token previously and rtMatches value should be false, but it is true. And i can call this endpoint endlessly and rtMatches will always remain true.

For more context:

async updateRefreshToken(userId: number, rt: string) {
    const hash = await this.hashData(rt)
    await this.adminService.updateRefreshToken(userId, hash)
}
async hashData(data: string) {
    return await argon2.hash(data)
}

If you need more detailed data i will gladly provide it, and sorry if I have some mistakes in explanation. I am not English native speaker

P.S.

I forgot to write why it almost always returns true on verify.

async updateRefreshToken(userId: number, rt: string) {
    const hash = await this.hashData(rt)
    const first = await argon2.verify(hash, rt) // always returns true
    const second = await argon2.verify(hash, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsInVzZXJOYW1lIjoiMTIzMTIzIiwiaWF0IjoxNzM3ODM5NzA1LCJleHAiOjE3Mzg0NDQ1MDV9.N8_5IA4GCDbxQg7Wda8JS78Qm0ojesgFyZqlW_00nR0') // always returns false
    console.log(first, 1)
    console.log(second, 2)
    console.log('******************')
    await this.adminService.updateRefreshToken(userId, hash)
}

when i used this logs to debug I saw an interesting bug. In modified updateRefreshToken method above rt is euqal to token in string format below, so they are the same value but on verify return different results. I don't know why this works like this.


Solution

  • I started developing frontend and tried there to login and then call refresh endpoint from browser and it worked, so there must have been some error with postman I think. Now all works as i wanted.