javascript - Använda 'nestjs/jwt' signering med dynamisk/användarrelaterad hemlighet
Jag försöker skapa en användartoken baserad på hemligheten för användaren som försöker logga in. Men istället för att använda en hemlighet från miljön vill jag använda en hemlighet som tilldelats ett användarobjekt i databasen.
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '@src/modules/user/services';
@Injectable()
export class AuthService {
public constructor(private readonly jwtService: JwtService,
private readonly userService: UserService) {}
public async createToken(email: string): Promise<JwtReply> {
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user({ where: { email } });
const accessToken = await this.jwtService.signAsync({ email: user.email },
/* user.secret ,*/
{ expiresIn });
return {
accessToken,
expiresIn,
};
}
}
Jag är ny på Nestjs och kanske missar jag något. node-jsonwebtoken tillhandahåller nödvändiga parametern i funktionen sign(...). nestjs/jwt saknar denna parameter (se kod). Hur skulle du lösa det utan att använda node-jsonwebtoken eller kanske en mer abstrakt fråga: Är mitt sätt att hantera användarens hemlighet vettigt här? Tack.
Svar #1
Detta är ännu inte möjligt enbart med nests JwtModule, men du kan enkelt implementera de saknade delarna själv.
Live-demonstration
Du kan skapa tokens genom att anropa följande rutter:
user1 (hemlighet: '123'): https://yw7wz99zv1.sse.codesandbox.io/login/1
user2 (hemlighet: '456'): https://yw7wz99zv1.sse.codesandbox.io/login/2
Anropa sedan den skyddade rutten '/' med din token och ta emot din användare:
curl -X GET https://yw7wz99zv1.sse.codesandbox.io/ \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiaWF0IjoxNTUzNjQwMjc5fQ.E5o3djesqWVHNGe-Hi3KODp0aTiQU9X_H3Murht1R5U'
Hur fungerar det?
I AuthService använder jag bara det standard jsonwebtoken-biblioteket för att skapa token. Du kan sedan kalla createToken från din inloggningsrutt:
import * as jwt from 'jsonwebtoken';
export class AuthService {
constructor(private readonly userService: UserService) {}
createToken(userId: string) {
const user = this.userService.getUser(userId);
return jwt.sign({ userId: user.userId }, user.secret, { expiresIn: 3600 });
}
// ...
}
I JwtStrategy använder du secretOrKeyProvider istället för secretOrKey som kan asynkront få åtkomst till UserService för att hämta användarens hemlighet dynamiskt:
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKeyProvider: (request, jwtToken, done) => {
const decodedToken: any = jwt.decode(jwtToken);
const user = this.userService.getUser(decodedToken.userId);
done(null, user.secret);
},
});
}
// ...
}
Observera att de alternativ du skickar till JwtModule som expiresIn inte kommer att användas, istället ska du direkt skicka dina alternativ i AuthService. Importera JwtModule utan några alternativ:
JwtModule.register({})
Allmän
Får mitt sätt att hantera användarhemligheter någon mening här?
Detta är svårt att svara på utan att veta dina exakta krav. Jag antar att det finns användningsfall för JWT med dynamiska hemligheter, men med det förlorar du en fantastisk egenskap hos JWT: de är statslösa. Det innebär att din AuthService kan utfärda en JWT-token och någon ProductService som kräver autentisering kan helt enkelt lita på JWT:n (den känner till hemligheten) utan att göra några anrop till andra tjänster (dvs. UserService som måste göra en databasfråga).
Om användarrelaterade nycklar inte är ett strikt krav, överväg istället att rotera nycklarna ofta genom att använda jwt:s kid-egenskap.
Svar #2
Alternativet att lägga till secret i JwtSignOptions har lagts till i nestjs/jwt version 7.1.0.
Med det skulle exemplet vara:
public async createToken(email: string): Promise<JwtReply> {
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user({ where: { email } });
const accessToken = await this.jwtService.signAsync(
{ email: user.email },
{ expiresIn,
secret: user.secret,
});
return {
accessToken,
expiresIn,
};
}