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