Nyligen har jag funderat på hur jag kan göra några av mina avgörande slutpunkter säkrare när jag använder lokalt lagrad JWT.
Det kanske inte är den bästa praxisen för säkerhet (på grund av möjligheten för XSS-attacker), men detta var ett krav som inte ställts av mig. Jag var tvungen att anpassa mig.
Så för att göra detta säkrare har jag hittat en lösning som förhoppningsvis också kommer att hjälpa dig.
Jag antar att vi alla vet vad JWT är - en token utfärdad av backend-tjänsten, som inte kan modifieras av frontend, eftersom det skulle ändra signaturen och automatiskt ogiltigförklara token.
Det låter bra, tills vi möter situationen där token kan bli stulen - som i mitt fall med XSS-attack. Många webbplatser lagrar tokens i lokal lagring istället för endast HTTP-cookie, så de är också sårbara.
Så när det inte är möjligt att ändra hur frontend lagrar den token, måste vi ändra sättet vi utfärdar och validerar token, och detta är ögonblicket då jag vill introducera dig för en enkel lösning - JWT med hashat fingeravtryck, men låt oss se koden
Låt oss anta att vi har en slutpunkt för att utfärda tokens, låt oss kalla det inloggningsslutpunkt
@Controller('v1/sign-in') export class SignInAction { constructor(private jwtService: JwtService) {} @Post() async handle( @Req() request: Request, @Body() body: SignInHttpRequest, ): Promise<SignInHttpResponse> { const ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress; const userAgent = request.headers['user-agent']; const token = this.jwtService.sign({ email: body.email, fingerprint: getSHA512Hash(`${ip}${userAgent}`), // It's worth to mention, that to make it even more secure, you should add salt }); return { token, }; } }
Så vad vi gör här är i princip att samla in användaragent och fjärr-IP från användare som ansluter, hasha det och sedan lägga in det i JWT-token, så även om användare eller angripare skulle vilja se vad som finns inuti JWT-token, kommer de bara att se något slumpmässigt hashat värde
Men hur kommer det att öka min säkerhet undrar du? Låt oss ta en titt på vår andra slutpunkt, visa fingeravtrycket
@Controller('v1/fingerprint') @UseGuards(AuthGuard(JWT_STRATEGY)) export class ShowFingerPrintAction { @Get() async handle(@Req() request) { return { fingerprint: request.user.fingerprint, }; } }
Till en början är det inget speciellt. Slutpunkt som returnerar fingeravtryck från begäran, så vad gör det säkrare? JWT_STRATEGI!
export const JWT_STRATEGY = 'JWT'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, JWT_STRATEGY) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'hard!to-guess_secret', passReqToCallback: true, }); } async validate(request: Request, payload: any): Promise<any> { const fingerprint = payload.fingerprint; const ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress; const userAgent = request.headers['user-agent']; const calculatedFingerprint = getSHA512Hash(`${ip}${userAgent}`); // It's worth to mention, that to make it even more secure, you should add salt if (fingerprint !== calculatedFingerprint) { throw new BadRequestException('Invalid fingerprint'); } return payload; } }
Som du kan se, i strategi, extraherar vi samma värden som när vi extraherade dem från inloggningsslutpunkten och jämför dem - om IP-adressen eller användaragenten har ändrats, kommer vi att neka åtkomst till vår fingeravtrycksslutpunkt.
Detta är fallet där även om angriparen stal vår token, kan han inte göra något med den, eftersom
Det är så vi kan skydda vår avgörande slutpunkt på ett mycket enkelt sätt, men naturligtvis finns det några avvägningar
För det första problemet kan du naturligtvis bara kontrollera IP och inte ens hasha den när du utfärdar en token, men då känner den potentiella angriparen till vektorn för attacken (IP-spoofing eller andra metoder), så det är säkrare att offra en del prestanda för säkerheten.
Jag hoppas att den här metoden kommer att hjälpa dig att introducera ett extra lager av säkerhet för dina avgörande slutpunkter.
Länk till fungerande källkod: Källkod
Tips: