diff --git a/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts b/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts index cae9b90fed2a..15e2fd9a4c2d 100644 --- a/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts +++ b/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts @@ -54,7 +54,7 @@ describe('jwt authentication', () => { it(`user login and token granted successfully`, async () => { const credentials = {email: 'jane@doe.com', password: 'opensesame'}; const res = await client - .post('/users/refresh-token') + .post('/users/refresh-login') .send(credentials) .expect(200); refreshToken = res.body.refreshToken; diff --git a/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts b/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts index 52ff0620fd12..41a7a5935601 100644 --- a/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts +++ b/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts @@ -14,8 +14,6 @@ import {get, post, requestBody} from '@loopback/rest'; import {SecurityBindings, securityId, UserProfile} from '@loopback/security'; import {genSalt, hash} from 'bcryptjs'; import { - RefreshGrant, - RefreshGrantRequestBody, RefreshTokenServiceBindings, TokenObject, TokenServiceBindings, @@ -26,6 +24,32 @@ import {UserRepository} from '../../../repositories'; import {Credentials} from '../../../services/user.service'; import {RefreshTokenService} from '../../../types'; +// Describes the type of grant object taken in by method "refresh" +type RefreshGrant = { + refreshToken: string; +}; + +// Describes the schema of grant object +const RefreshGrantSchema = { + type: 'object', + required: ['refreshToken'], + properties: { + refreshToken: { + type: 'string', + }, + }, +}; + +// Describes the request body of grant object +const RefreshGrantRequestBody = { + description: 'Reissuing Acess Token', + required: true, + content: { + 'application/json': {schema: RefreshGrantSchema}, + }, +}; + +// Describe the schema of user credentials const CredentialsSchema = { type: 'object', required: ['email', 'password'], @@ -105,6 +129,11 @@ export class UserController { return savedUser; } + /** + * A login function that returns an access token. After login, include the token + * in the next requests to verify your identity. + * @param credentials User email and password + */ @post('/users/login', { responses: { '200': { @@ -153,8 +182,11 @@ export class UserController { async whoAmI(): Promise { return this.user[securityId]; } - // Routes using refreshtoken - @post('/users/refresh-token', { + /** + * A login function that returns refresh token and access token. + * @param credentials User email and password + */ + @post('/users/refresh-login', { responses: { '200': { description: 'Token', diff --git a/extensions/authentication-jwt/src/jwt-authentication-component.ts b/extensions/authentication-jwt/src/jwt-authentication-component.ts index 8944c11286a1..1517bcad7674 100644 --- a/extensions/authentication-jwt/src/jwt-authentication-component.ts +++ b/extensions/authentication-jwt/src/jwt-authentication-component.ts @@ -13,16 +13,16 @@ import { inject, } from '@loopback/core'; import { + RefreshTokenConstants, + RefreshTokenServiceBindings, TokenServiceBindings, TokenServiceConstants, UserServiceBindings, - RefreshTokenConstants, - RefreshTokenServiceBindings, } from './keys'; import { + RefreshTokenRepository, UserCredentialsRepository, UserRepository, - RefreshTokenRepository, } from './repositories'; import {MyUserService, RefreshtokenService} from './services'; import {JWTAuthenticationStrategy} from './services/jwt.auth.strategy'; diff --git a/extensions/authentication-jwt/src/keys.ts b/extensions/authentication-jwt/src/keys.ts index 0df87b0bab02..fde930855b15 100644 --- a/extensions/authentication-jwt/src/keys.ts +++ b/extensions/authentication-jwt/src/keys.ts @@ -35,12 +35,29 @@ export namespace UserServiceBindings { export const USER_CREDENTIALS_REPOSITORY = 'repositories.UserCredentialsRepository'; } + +/** + * Constant values used when generating refresh token. + */ export namespace RefreshTokenConstants { + /** + * The default secret used when generating refresh token. + */ export const REFRESH_SECRET_VALUE = 'r3fr35htok3n'; + /** + * The default expiration time for refresh token. + */ export const REFRESH_EXPIRES_IN_VALUE = '216000'; + /** + * The default issure used when generating refresh token. + */ export const REFRESH_ISSURE_VALUE = 'loopback4'; } +/** + * Bindings related to token refresh service. The omitted explanation can be + * found in namespace `RefreshTokenConstants`. + */ export namespace RefreshTokenServiceBindings { export const REFRESH_TOKEN_SERVICE = BindingKey.create( 'services.authentication.jwt.refresh.tokenservice', @@ -54,6 +71,13 @@ export namespace RefreshTokenServiceBindings { export const REFRESH_ISSURE = BindingKey.create( 'authentication.jwt.referesh.issure', ); + /** + * The backend datasource for refresh token's persistency. + */ export const DATASOURCE_NAME = 'refreshdb'; + /** + * Key for the repository that stores the refresh token and its bound user + * information + */ export const REFRESH_REPOSITORY = 'repositories.RefreshTokenRepository'; } diff --git a/extensions/authentication-jwt/src/services/refreshtoken.service.ts b/extensions/authentication-jwt/src/services/refreshtoken.service.ts index f0bdcfcd0ab3..a2f01e897015 100644 --- a/extensions/authentication-jwt/src/services/refreshtoken.service.ts +++ b/extensions/authentication-jwt/src/services/refreshtoken.service.ts @@ -9,13 +9,11 @@ import { TokenServiceBindings, UserServiceBindings, } from '../keys'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import {RefreshToken, RefereshTokenWithRelations} from '../models'; import {RefreshTokenRepository} from '../repositories'; import {TokenObject} from '../types'; import {MyUserService} from './user.service'; -/* eslint-disable*/ - -import {RefreshToken, RefreshTokenRelations} from '../models'; -/* eslint-enable */ const jwt = require('jsonwebtoken'); const signAsync = promisify(jwt.sign); const verifyAsync = promisify(jwt.verify); @@ -34,9 +32,9 @@ export class RefreshtokenService { @inject(UserServiceBindings.USER_SERVICE) public userService: MyUserService, @inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService, ) {} - /* - * Generate Refresh Token - * UserProfile and roken generated by JWTService + /** + * Generate a refresh token, bind it with the given user profile + access + * token, then store them in backend. */ async generateToken( userProfile: UserProfile, @@ -59,9 +57,9 @@ export class RefreshtokenService { }); return result; } + /* - * Refresh JWT Token - * Issued refreshToken + * Refresh the access token bound with the given refresh token. */ async refreshToken(refreshToken: string): Promise { try { @@ -90,6 +88,7 @@ export class RefreshtokenService { ); } } + /* * [TODO] test and endpoint */ @@ -102,6 +101,11 @@ export class RefreshtokenService { // ignore } } + + /** + * Verify the validity of a refresh token, and make sure it exists in backend. + * @param refreshToken + */ async verifyToken(refreshToken: string) { try { await verifyAsync(refreshToken, this.refreshSecret); diff --git a/extensions/authentication-jwt/src/types.ts b/extensions/authentication-jwt/src/types.ts index 30555f69f1ca..78601539cb5a 100644 --- a/extensions/authentication-jwt/src/types.ts +++ b/extensions/authentication-jwt/src/types.ts @@ -5,41 +5,29 @@ import {UserProfile} from '@loopback/security'; -export type RefreshGrant = { - refreshToken: string; -}; - -export const RefreshGrantSchema = { - type: 'object', - required: ['refreshToken'], - properties: { - refreshToken: { - type: 'string', - }, - }, -}; -export const RefreshGrantRequestBody = { - description: 'Reissuing Acess Token', - required: true, - content: { - 'application/json': {schema: RefreshGrantSchema}, - }, -}; - +/** + * Describes the token object that returned by the refresh token service functions. + */ export type TokenObject = { accessToken: string; expiresIn?: string | undefined; refreshToken?: string | undefined; }; +/** + * The token refresh service. An access token expires in limited time. Therefore + * token refresh service is needed to keep replacing the old access token with + * a new one periodically. + */ export interface RefreshTokenService { /** - * Generate Token and return the Token Object + * Generate a refresh token, bind it with the given user profile + access + * token, then store them in backend. */ generateToken(userProfile: UserProfile, token: string): Promise; + /** - * Verifies the validity of a token string and returns a new Token - * + * Refresh the access token bound with the given refresh token. */ refreshToken(refreshToken: string): Promise; }