lbx-jwt
Version:
Provides JWT authentication for loopback applications. Includes storing roles inside tokens and handling refreshing. Built-in reuse detection.
67 lines (63 loc) • 2.75 kB
text/typescript
import { TokenService } from '@loopback/authentication';
import { inject } from '@loopback/core';
import { HttpErrors } from '@loopback/rest';
import { securityId } from '@loopback/security';
import { convertMsToSeconds } from './convert-ms-to-seconds.function';
import { EncodedJwt, JwtUtilities } from '../encapsulation/jwt.utilities';
import { LbxJwtBindings } from '../keys';
import { BaseUserProfile } from '../models/base-user-profile.model';
import { JwtPayload } from '../models/jwt.model';
/**
* Generates and verifies access tokens.
*/
export class AccessTokenService<RoleType extends string> implements TokenService {
constructor(
(LbxJwtBindings.ACCESS_TOKEN_SECRET)
private readonly accessTokenSecret: string,
(LbxJwtBindings.ACCESS_TOKEN_EXPIRES_IN_MS)
private readonly accessTokenExpiresInMs: number
) {}
// eslint-disable-next-line jsdoc/require-jsdoc
async verifyToken(token: string): Promise<BaseUserProfile<RoleType>> {
try {
const decodedToken: EncodedJwt<RoleType> = await JwtUtilities.verifyAsync(token, this.accessTokenSecret);
// don't copy over token field 'iat' and 'exp', nor 'email' to user profile
const userProfile: Omit<BaseUserProfile<RoleType>, 'email'> = Object.assign(
{
[securityId]: decodedToken.payload.id,
name: ''
},
{
id: decodedToken.payload.id,
roles: decodedToken.payload.roles
}
);
return userProfile as BaseUserProfile<RoleType>;
}
catch (error) {
// eslint-disable-next-line typescript/no-unsafe-member-access
throw new HttpErrors.Unauthorized(`Error verifying access token: ${error.message}`);
}
}
// eslint-disable-next-line jsdoc/require-jsdoc
async generateToken(userProfile: BaseUserProfile<RoleType>): Promise<string> {
// eslint-disable-next-line jsdoc/require-jsdoc
const jwtPayload: JwtPayload<RoleType> & { email: string } = {
id: userProfile[securityId],
email: userProfile.email,
roles: userProfile.roles
};
// Generate a JSON Web Token
try {
return await JwtUtilities.signAsync(
jwtPayload,
this.accessTokenSecret,
{ expiresIn: convertMsToSeconds(this.accessTokenExpiresInMs) }
);
}
catch (error) {
// eslint-disable-next-line typescript/no-unsafe-member-access
throw new HttpErrors.Unauthorized(`Error generating token: ${error.message}`);
}
}
}