Last active
November 22, 2023 06:41
-
-
Save Jhoem/0234badd1ca41b0658f2cd78e00ec682 to your computer and use it in GitHub Desktop.
Sample logout on nestjs using Cache manager w/ Redis for invalidating tokens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async logout({ jwt }) { | |
const { token, exp: expirationTime = 0 } = jwt; | |
// Convert current date to timestamp in seconds | |
const currentTimestamp = Math.floor(Date.now() / 1000); | |
const remainingTTL = expirationTime - currentTimestamp; | |
if (remainingTTL <= 0) { | |
throw new UnauthorizedException(AuthErrors.tokenExpired); | |
} | |
// Note that multiple jwt tokens can exist at any given time | |
// Blacklist the token until it expires base from it's original `exp` | |
await this.cacheManager.set( | |
`${jwtConstants.prefixExpired}${token}`, | |
true, | |
remainingTTL, | |
); | |
return { | |
status: 'success', | |
message: Messages.loggedOut, | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { PassportStrategy } from '@nestjs/passport'; | |
import { JwtService } from '@nestjs/jwt'; | |
import { | |
Injectable, Inject, UnauthorizedException, CACHE_MANAGER, | |
} from '@nestjs/common'; | |
import { ExtractJwt, Strategy } from 'passport-jwt'; | |
import { Cache } from 'cache-manager'; | |
import { jwtConstants } from 'src/config/auth'; | |
import { AuthErrors } from 'src/common/translation'; | |
import { JwtSession } from 'src/common/interfaces/jwt_session'; | |
@Injectable() | |
export class JwtStrategy extends PassportStrategy(Strategy) { | |
constructor( | |
@Inject(CACHE_MANAGER) private cacheManager: Cache, | |
private jwtService: JwtService, | |
) { | |
super({ | |
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), | |
ignoreExpiration: false, | |
secretOrKey: jwtConstants.options.secret, | |
passReqToCallback: true, | |
}); | |
} | |
async validate(req, { sub: userId, exp }) { | |
const getJwtToken = ExtractJwt.fromAuthHeaderAsBearerToken(); | |
const jwtToken = getJwtToken(req); | |
const cachedSessionId = `${jwtConstants.prefix}${userId}`; | |
const expiredJwtId = `${jwtConstants.prefixExpired}${jwtToken}`; | |
const isTokenExpired = await this.cacheManager.get<boolean>(expiredJwtId); | |
const jwtSession = await this.cacheManager.get<JwtSession>(cachedSessionId); | |
// Check if token has expired or has no active session | |
if (isTokenExpired || !jwtSession) { | |
throw new UnauthorizedException( | |
isTokenExpired ? AuthErrors.tokenExpired : AuthErrors.tokenInvalid, | |
); | |
} | |
// Attach `user` property to `req` | |
return { | |
userId, | |
role: jwtSession.role, | |
jwt: { | |
token: jwtToken, | |
// current token's expiration | |
exp, | |
}, | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment