A LoopBack 4 application that uses JWT authentication

Overview

LoopBack 4 has an authentication package @loopback/authentication which allowsyou to secure your application’s API endpoints with custom authenticationstrategies and an @authenticate decorator.

This tutorial showcases how authentication was added to theloopback4-example-shoppingapplication by creating and registering a custom authentication strategybased on the JSON Web Token (JWT) approach.

Here is a brief summary of the JSON Web Token (JWT) approach.

JSON Web Token Authentication Overview

In the JSON Web Token (JWT) authentication approach, when the user providesthe correct credentials to a login endpoint, the server creates a JWTtoken and returns it in the response. The token is of type string andconsists of 3 parts: the header, the payload, and the signature.Each part is encrypted using a secret, and the parts are separated by aperiod.

For example:

  1. // {encrypted-header}.{encrypted-payload}.{encrypted-signature}
  2. eyJhbXVCJ9.eyJpZCI6Ij.I3wpRNCH4;
  3. // actual parts have been reduced in size for viewing purposes

Note:The payload can contain anythingthe application developer wants, but at the very least contains the user id. It should never contain the user password.

After logging in and obtaining this token, whenever the user attempts to accessa protected endpoint, the token must be provided in the Authorizationheader. The server verifies that the token is valid and not expired, and thenpermits access to the protected endpoint.

Please see JSON Web Token (JWT)for more details.

To view and run the completed loopback4-example-shopping application, followthe instructions in the Try it out section.

To understand the details of how JWT authentication can be added to a LoopBack 4application, read theAdding JWT Authentication to a LoopBack 4 Applicationsection.

Try it out

If you’d like to see the final results of this tutorial as an exampleapplication, follow these steps:

  • Start the application:
  1. git clone https://github.com/strongloop/loopback4-example-shopping.git
  2. cd loopback4-example-shopping
  3. npm install
  4. npm run docker:start
  5. npm start

Wait until you see:

  1. Recommendation server is running at http://127.0.0.1:3001.
  2. Server is running at http://[::1]:3000
  3. Try http://[::1]:3000/ping
  • In a browser, navigate to http://[::1]:3000 orhttp://127.0.0.1:3000, and click on /explorer toopen the API Explorer.

  • In the UserController section, click on POST /users, click on'Try it out', specify:

  1. {
  2. "id": "1",
  3. "email": "user1@example.com",
  4. "password": "thel0ngp@55w0rd",
  5. "firstName": "User",
  6. "lastName": "One"
  7. }

and click on 'Execute' to add a new user named 'User One'.

  • In the UserController section, click on POST /users/login, click on'Try it out', specify:
  1. {
  2. "email": "user1@example.com",
  3. "password": "thel0ngp@55w0rd"
  4. }

and click on 'Execute' to log in as 'User One'.

A JWT token is sent back in the response.

For example:

  1. {
  2. "token": "some.token.value"
  3. }
  • Perform a GET request on the secured endpoint /users/me making sure toprovide the JWT token in the Authorization header. If authenticationsucceeds, theuser profileof the currently authenticated user will be returned in the response. Ifauthentication fails due to a missing/invalid/expired token, anHTTP 401 UnAuthorizedis thrown.
  1. curl -X GET \
  2. --header 'Authorization: Bearer some.token.value' \
  3. http://127.0.0.1:3000/users/me

The response is:

  1. {"id":"1","name":"User One"}

Adding JWT Authentication to a LoopBack 4 Application

In this section, we will demonstrate how authentication was added to theloopback4-example-shoppingapplication using theJSON Web Token (JWT) approach.

Installing @loopback/authentication

The loopback4-example-shopping application already has the@loopback/authentication dependency set up in its package.json

It was installed as a project dependency by performing:

  1. npm install --save @loopback/authentication

Adding the AuthenticationComponent to the Application

The core of authentication framework is found in theAuthenticationComponent,so it is important to add the component in the ShoppingApplication class inloopback4-example-shopping/packages/shopping/src/application.ts.

  1. import {
  2. AuthenticationComponent
  3. } from '@loopback/authentication';
  4. export class ShoppingApplication extends BootMixin(
  5. ServiceMixin(RepositoryMixin(RestApplication)),
  6. ) {
  7. constructor(options?: ApplicationConfig) {
  8. super(options);
  9. // ...
  10. // Bind authentication component related elements
  11. this.component(AuthenticationComponent);
  12. // ...

Securing an Endpoint with the Authentication Decorator

Securing your application’s API endpoints is done by decorating controllerfunctions with theAuthentication Decorator.

The decorator’s syntax is:

  1. @authenticate(strategyName: string, options?: object)

In the loopback4-example-shopping application, there is only one endpoint thatis secured.

In the UserController class in theloopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts,a user can print out his/her user profile by performing a GET request on the/users/me endpoint which is handled by the printCurrentUser() function.

  1. // ...
  2. @get('/users/me', {
  3. responses: {
  4. '200': {
  5. description: 'The current user profile',
  6. content: {
  7. 'application/json': {
  8. schema: UserProfileSchema,
  9. },
  10. },
  11. },
  12. },
  13. })
  14. @authenticate('jwt')
  15. async printCurrentUser(
  16. @inject(SecurityBindings.USER)
  17. currentUserProfile: UserProfile,
  18. ): Promise<UserProfile> {
  19. return currentUserProfile;
  20. }
  21. // ...

Note:Since this controller method is obtaining CURRENT_USER via method injection (instead of constructor injection) and this method is decorated with the @authenticate decorator, there is no need to specify @inject(SecurityBindings.USER, {optional:true}). See Using the Authentication Decorator for details.

The /users/me endpoint is decorated with

  1. @authenticate('jwt')

and authentication will only succeed if a valid JWT token is provided in theAuthorization header of the request.

Basically, theAuthenticateFnaction in the custom sequence MyAuthenticationSequence (discussed in a latersection) asksAuthenticationStrategyProviderto resolve the registered authentication strategy with the name 'jwt' (whichis JWTAuthenticationStrategy and discussed in a later section). ThenAuthenticateFn calls JWTAuthenticationStrategy’s authenticate(request)function to authenticate the request.

If the provided JWT token is valid, then JWTAuthenticationStrategy’sauthenticate(request) function returns the user profile. AuthenticateFn thenplaces the user profile on the request context using the SecurityBindings.USERbinding key. The user profile is available to the printCurrentUser()controller function in a variable currentUserProfile: UserProfile throughdependency injection via the same SecurityBindings.USER binding key. The userprofile is returned in the response.

If the JWT token is missing/expired/invalid, then JWTAuthenticationStrategy’sauthenticate(request) function fails and anHTTP 401 UnAuthorizedis thrown.

If an unknown authentication strategy name is specified in the@authenticate decorator:

  1. @authenticate('unknown')

thenAuthenticationStrategyProvider’sfindAuthenticationStrategy(name: string) function cannot find a registeredauthentication strategy by that name, and anHTTP 401 UnAuthorizedis thrown.

So, be sure to specify the correct authentication strategy name when decoratingyour endpoints with the @authenticate decorator.

Creating a Custom Sequence and Adding the Authentication Action

In a LoopBack 4 application with REST API endpoints, each request passes througha stateless grouping of actions called a Sequence.

Authentication is not part of the default sequence of actions, so you mustcreate a custom sequence and add the authentication action.

The custom sequence MyAuthenticationSequence inloopback4-example-shopping/packages/shopping/src/sequence.ts implements theSequenceHandlerinterface.

  1. export class MyAuthenticationSequence implements SequenceHandler {
  2. constructor(
  3. @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
  4. @inject(SequenceActions.PARSE_PARAMS)
  5. protected parseParams: ParseParams,
  6. @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
  7. @inject(SequenceActions.SEND) protected send: Send,
  8. @inject(SequenceActions.REJECT) protected reject: Reject,
  9. @inject(AuthenticationBindings.AUTH_ACTION)
  10. protected authenticateRequest: AuthenticateFn,
  11. ) {}
  12. async handle(context: RequestContext) {
  13. try {
  14. const {request, response} = context;
  15. const route = this.findRoute(request);
  16. //call authentication action
  17. await this.authenticateRequest(request);
  18. // Authentication successful, proceed to invoke controller
  19. const args = await this.parseParams(request, route);
  20. const result = await this.invoke(route, args);
  21. this.send(response, result);
  22. } catch (error) {
  23. //
  24. // The authentication action utilizes a strategy resolver to find
  25. // an authentication strategy by name, and then it calls
  26. // strategy.authenticate(request).
  27. //
  28. // The strategy resolver throws a non-http error if it cannot
  29. // resolve the strategy. When the strategy resolver obtains
  30. // a strategy, it calls strategy.authenticate(request) which
  31. // is expected to return a user profile. If the user profile
  32. // is undefined, then it throws a non-http error.
  33. //
  34. // It is necessary to catch these errors and add HTTP-specific status
  35. // code property.
  36. //
  37. // Errors thrown by the strategy implementations already come
  38. // with statusCode set.
  39. //
  40. // In the future, we want to improve `@loopback/rest` to provide
  41. // an extension point allowing `@loopback/authentication` to contribute
  42. // mappings from error codes to HTTP status codes, so that application
  43. // don't have to map codes themselves.
  44. if (
  45. error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
  46. error.code === USER_PROFILE_NOT_FOUND
  47. ) {
  48. Object.assign(error, {statusCode: 401 /* Unauthorized */});
  49. }
  50. this.reject(context, error);
  51. return;
  52. }
  53. }
  54. }

The authentication action/function is injected via theAuthenticationBindings.AUTH_ACTION binding key, is given the nameauthenticateRequest and has the typeAuthenticateFn.

Calling

  1. await this.authenticateRequest(request);

before

  1. // ...
  2. const result = await this.invoke(route, args);
  3. this.send(response, result);
  4. // ...

ensures that authentication has succeeded before a controller endpoint isreached.

To add the custom sequence MyAuthenticationSequence in the application, wemust code the following inloopback4-example-shopping/packages/shopping/src/application.ts:

  1. export class ShoppingApplication extends BootMixin(
  2. ServiceMixin(RepositoryMixin(RestApplication)),
  3. ) {
  4. constructor(options?: ApplicationConfig) {
  5. super(options);
  6. // ...
  7. // Set up the custom sequence
  8. this.sequence(MyAuthenticationSequence);
  9. // ...
  10. }
  11. }

Creating a Custom JWT Authentication Strategy

When creating a custom authentication strategy, it is necessary to implement theAuthenticationStrategyinterface.

A custom JWT authentication strategy JWTAuthenticationStrategy inloopback4-example-shopping/packages/shopping/src/authentication-strategies/jwt-strategy.tswas implemented as follows:

  1. import {inject} from '@loopback/context';
  2. import {HttpErrors, Request} from '@loopback/rest';
  3. import {
  4. AuthenticationStrategy,
  5. UserProfile,
  6. TokenService,
  7. } from '@loopback/authentication';
  8. import {TokenServiceBindings} from '../keys';
  9. export class JWTAuthenticationStrategy implements AuthenticationStrategy {
  10. name: string = 'jwt';
  11. constructor(
  12. @inject(TokenServiceBindings.TOKEN_SERVICE)
  13. public tokenService: TokenService,
  14. ) {}
  15. async authenticate(request: Request): Promise<UserProfile | undefined> {
  16. const token: string = this.extractCredentials(request);
  17. const userProfile: UserProfile = await this.tokenService.verifyToken(token);
  18. return userProfile;
  19. }
  20. extractCredentials(request: Request): string {
  21. if (!request.headers.authorization) {
  22. throw new HttpErrors.Unauthorized(`Authorization header not found.`);
  23. }
  24. // for example: Bearer xxx.yyy.zzz
  25. const authHeaderValue = request.headers.authorization;
  26. if (!authHeaderValue.startsWith('Bearer')) {
  27. throw new HttpErrors.Unauthorized(
  28. `Authorization header is not of type 'Bearer'.`,
  29. );
  30. }
  31. //split the string into 2 parts: 'Bearer ' and the `xxx.yyy.zzz`
  32. const parts = authHeaderValue.split(' ');
  33. if (parts.length !== 2)
  34. throw new HttpErrors.Unauthorized(
  35. `Authorization header value has too many parts. It must follow the pattern: 'Bearer xx.yy.zz' where xx.yy.zz is a valid JWT token.`,
  36. );
  37. const token = parts[1];
  38. return token;
  39. }
  40. }

It has a name 'jwt', and it implements theasync authenticate(request: Request): Promise<UserProfile | undefined>function.

An extra function extractCredentials(request: Request): string was added toextract the JWT token from the request. This authentication strategy expectsevery request to pass a valid JWT token in the Authorization header.

JWTAuthenticationStrategy also makes use of a token service tokenService oftype TokenService that is injected via theTokenServiceBindings.TOKEN_SERVICE binding key. It is used to verify thevalidity of the JWT token and return a user profile.

This token service is explained in a later section.

Registering the Custom JWT Authentication Strategy

To register the custom authentication strategy JWTAuthenticationStrategy withthe name 'jwt' as a part of the authentication framework, we need to codethe following inloopback4-example-shopping/packages/shopping/src/application.ts.

  1. import {registerAuthenticationStrategy} from '@loopback/authentication';
  2. export class ShoppingApplication extends BootMixin(
  3. ServiceMixin(RepositoryMixin(RestApplication)),
  4. ) {
  5. constructor(options?: ApplicationConfig) {
  6. super(options);
  7. // ...
  8. registerAuthenticationStrategy(this, JWTAuthenticationStrategy);
  9. // ...
  10. }
  11. }

Creating a Token Service

The token service JWTService inloopback4-example-shopping/packages/shopping/src/services/jwt-service.tsimplements an optional helperTokenServiceinterface.

  1. import {inject} from '@loopback/context';
  2. import {HttpErrors} from '@loopback/rest';
  3. import {promisify} from 'util';
  4. import {TokenService, UserProfile} from '@loopback/authentication';
  5. import {TokenServiceBindings} from '../keys';
  6. const jwt = require('jsonwebtoken');
  7. const signAsync = promisify(jwt.sign);
  8. const verifyAsync = promisify(jwt.verify);
  9. export class JWTService implements TokenService {
  10. constructor(
  11. @inject(TokenServiceBindings.TOKEN_SECRET)
  12. private jwtSecret: string,
  13. @inject(TokenServiceBindings.TOKEN_EXPIRES_IN)
  14. private jwtExpiresIn: string,
  15. ) {}
  16. async verifyToken(token: string): Promise<UserProfile> {
  17. if (!token) {
  18. throw new HttpErrors.Unauthorized(
  19. `Error verifying token: 'token' is null`,
  20. );
  21. }
  22. let userProfile: UserProfile;
  23. try {
  24. // decode user profile from token
  25. const decryptedToken = await verifyAsync(token, this.jwtSecret);
  26. // don't copy over token field 'iat' and 'exp', nor 'email' to user profile
  27. userProfile = Object.assign(
  28. {id: '', name: ''},
  29. {id: decryptedToken.id, name: decryptedToken.name},
  30. );
  31. } catch (error) {
  32. throw new HttpErrors.Unauthorized(
  33. `Error verifying token: ${error.message}`,
  34. );
  35. }
  36. return userProfile;
  37. }
  38. async generateToken(userProfile: UserProfile): Promise<string> {
  39. if (!userProfile) {
  40. throw new HttpErrors.Unauthorized(
  41. 'Error generating token: userProfile is null',
  42. );
  43. }
  44. // Generate a JSON Web Token
  45. let token: string;
  46. try {
  47. token = await signAsync(userProfile, this.jwtSecret, {
  48. expiresIn: Number(this.jwtExpiresIn),
  49. });
  50. } catch (error) {
  51. throw new HttpErrors.Unauthorized(`Error encoding token: ${error}`);
  52. }
  53. return token;
  54. }
  55. }

JWTService generates or verifies JWT tokens using the sign and verifyfunctions of jsonwebtoken.

It makes use of jwtSecret and jwtExpiresIn string values that areinjected via the TokenServiceBindings.TOKEN_SECRET and theTokenServiceBindings.TOKEN_EXPIRES_IN binding keys respectively.

The async generateToken(userProfile: UserProfile): Promise<string> functiontakes in a user profile of typeUserProfile,generates a JWT token of type string using: the user profile as thepayload, jwtSecret and jwtExpiresIn.

The async verifyToken(token: string): Promise<UserProfile> function takes in aJWT token of type string, verifies the JWT token, and returns the payload ofthe token which is a user profile of type UserProfile.

To bind the JWT secret, expires in values and the JWTService class tobinding keys, we need to code the following inloopback4-example-shopping/packages/shopping/src/application.ts:

  1. export class ShoppingApplication extends BootMixin(
  2. ServiceMixin(RepositoryMixin(RestApplication)),
  3. ) {
  4. constructor(options?: ApplicationConfig) {
  5. super(options);
  6. // ...
  7. this.setUpBindings();
  8. // ...
  9. }
  10. setUpBindings(): void {
  11. // ...
  12. this.bind(TokenServiceBindings.TOKEN_SECRET).to(
  13. TokenServiceConstants.TOKEN_SECRET_VALUE,
  14. );
  15. this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(
  16. TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
  17. );
  18. this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);
  19. // ...
  20. }
  21. }

In the code above, TOKEN_SECRET_VALUE has a value of 'myjwts3cr3t' andTOKEN_EXPIRES_IN_VALUE has a value of '600'.

JWTService is used in two places within the application:JWTAuthenticationStrategy inloopback4-example-shopping/packages/shopping/src/authentication-strategies/jwt-strategy.ts,and UserController inloopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts.

Creating a User Service

The user service MyUserService inloopback4-example-shopping/packages/shopping/src/services/user-service.tsimplements an optional helperUserServiceinterface.

  1. export class MyUserService implements UserService<User, Credentials> {
  2. constructor(
  3. @repository(UserRepository) public userRepository: UserRepository,
  4. @inject(PasswordHasherBindings.PASSWORD_HASHER)
  5. public passwordHasher: PasswordHasher,
  6. ) {}
  7. async verifyCredentials(credentials: Credentials): Promise<User> {
  8. const foundUser = await this.userRepository.findOne({
  9. where: {email: credentials.email},
  10. });
  11. if (!foundUser) {
  12. throw new HttpErrors.NotFound(
  13. `User with email ${credentials.email} not found.`,
  14. );
  15. }
  16. const passwordMatched = await this.passwordHasher.comparePassword(
  17. credentials.password,
  18. foundUser.password,
  19. );
  20. if (!passwordMatched) {
  21. throw new HttpErrors.Unauthorized('The credentials are not correct.');
  22. }
  23. return foundUser;
  24. }
  25. convertToUserProfile(user: User): UserProfile {
  26. // since first name and lastName are optional, no error is thrown if not provided
  27. let userName = '';
  28. if (user.firstName) userName = `${user.firstName}`;
  29. if (user.lastName)
  30. userName = user.firstName
  31. ? `${userName} ${user.lastName}`
  32. : `${user.lastName}`;
  33. return {id: user.id, name: userName};
  34. }
  35. }

The async verifyCredentials(credentials: Credentials): Promise<User> functiontakes in a credentials of typeCredentials,and returns a user of typeUser.It searches through an injected user repository of type UserRepository.

The convertToUserProfile(user: User): UserProfile function takes in a userof type User and returns a user profile of typeUserProfile.A user profile, in this case, is the minimum set of user properties whichindentify an authenticated user.

MyUserService is used in by UserController inloopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts.

To bind the MyUserService class, and the password hashing utility it uses, tobinding keys, we need to code the following inloopback4-example-shopping/packages/shopping/src/application.ts:

  1. export class ShoppingApplication extends BootMixin(
  2. ServiceMixin(RepositoryMixin(RestApplication)),
  3. ) {
  4. constructor(options?: ApplicationConfig) {
  5. super(options);
  6. // ...
  7. this.setUpBindings();
  8. // ...
  9. }
  10. setUpBindings(): void {
  11. // ...
  12. // Bind bcrypt hash services - utilized by 'UserController' and 'MyUserService'
  13. this.bind(PasswordHasherBindings.ROUNDS).to(10);
  14. this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher);
  15. this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);
  16. // ...
  17. }
  18. }

Adding Users

In the UserController class in theloopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts,users can be added by performing a POST request to the /users endpoint whichis handled by the create() function.

  1. export class UserController {
  2. constructor(
  3. // ...
  4. @repository(UserRepository) public userRepository: UserRepository,
  5. @inject(PasswordHasherBindings.PASSWORD_HASHER)
  6. public passwordHasher: PasswordHasher,
  7. @inject(TokenServiceBindings.TOKEN_SERVICE)
  8. public jwtService: TokenService,
  9. @inject(UserServiceBindings.USER_SERVICE)
  10. public userService: UserService<User, Credentials>,
  11. ) {}
  12. // ...
  13. @post('/users')
  14. async create(@requestBody() user: User): Promise<User> {
  15. // ensure a valid email value and password value
  16. validateCredentials(_.pick(user, ['email', 'password']));
  17. // encrypt the password
  18. user.password = await this.passwordHasher.hashPassword(user.password);
  19. // create the new user
  20. const savedUser = await this.userRepository.create(user);
  21. delete savedUser.password;
  22. return savedUser;
  23. }
  24. // ...

A user of typeUseris added to the database via the user repository if the user’s email andpassword values are in an acceptable format.

Issuing a JWT Token on Successful Login

In the UserController class in theloopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts,a user can log in by performing a POST request, containing an email andpassword, to the /users/login endpoint which is handled by the login()function.

  1. export class UserController {
  2. constructor(
  3. // ...
  4. @repository(UserRepository) public userRepository: UserRepository,
  5. @inject(PasswordHasherBindings.PASSWORD_HASHER)
  6. public passwordHasher: PasswordHasher,
  7. @inject(TokenServiceBindings.TOKEN_SERVICE)
  8. public jwtService: TokenService,
  9. @inject(UserServiceBindings.USER_SERVICE)
  10. public userService: UserService<User, Credentials>,
  11. ) {}
  12. // ...
  13. @post('/users/login', {
  14. responses: {
  15. '200': {
  16. description: 'Token',
  17. content: {
  18. 'application/json': {
  19. schema: {
  20. type: 'object',
  21. properties: {
  22. token: {
  23. type: 'string',
  24. },
  25. },
  26. },
  27. },
  28. },
  29. },
  30. },
  31. })
  32. async login(
  33. @requestBody(CredentialsRequestBody) credentials: Credentials,
  34. ): Promise<{token: string}> {
  35. // ensure the user exists, and the password is correct
  36. const user = await this.userService.verifyCredentials(credentials);
  37. // convert a User object into a UserProfile object (reduced set of properties)
  38. const userProfile = this.userService.convertToUserProfile(user);
  39. // create a JSON Web Token based on the user profile
  40. const token = await this.jwtService.generateToken(userProfile);
  41. return {token};
  42. }
  43. }

The user service returns a user object when the email and password are verifiedas valid; otherwise it throws anHTTP 401 UnAuthorized.The user service is then called to create a slimmer user profile from the userobject. Then this user profile is used as the payload of the JWT token createdby the token service. The token is returned in the response.

Summary

We’ve gone through the steps that were used to add JWT authentication to theloopback4-example-shopping application.

The final ShoppingApplication class inloopback4-example-shopping/packages/shopping/src/application.tsshould look like this:

  1. import {BootMixin} from '@loopback/boot';
  2. import {ApplicationConfig, BindingKey} from '@loopback/core';
  3. import {RepositoryMixin} from '@loopback/repository';
  4. import {RestApplication} from '@loopback/rest';
  5. import {ServiceMixin} from '@loopback/service-proxy';
  6. import {MyAuthenticationSequence} from './sequence';
  7. import {
  8. TokenServiceBindings,
  9. UserServiceBindings,
  10. TokenServiceConstants,
  11. } from './keys';
  12. import {JWTService} from './services/jwt-service';
  13. import {MyUserService} from './services/user-service';
  14. import * as path from 'path';
  15. import {
  16. AuthenticationComponent,
  17. registerAuthenticationStrategy,
  18. } from '@loopback/authentication';
  19. import {PasswordHasherBindings} from './keys';
  20. import {BcryptHasher} from './services/hash.password.bcryptjs';
  21. import {JWTAuthenticationStrategy} from './authentication-strategies/jwt-strategy';
  22. /**
  23. * Information from package.json
  24. */
  25. export interface PackageInfo {
  26. name: string;
  27. version: string;
  28. description: string;
  29. }
  30. export const PackageKey = BindingKey.create<PackageInfo>('application.package');
  31. const pkg: PackageInfo = require('../package.json');
  32. export class ShoppingApplication extends BootMixin(
  33. ServiceMixin(RepositoryMixin(RestApplication)),
  34. ) {
  35. constructor(options?: ApplicationConfig) {
  36. super(options);
  37. this.setUpBindings();
  38. // Bind authentication component related elements
  39. this.component(AuthenticationComponent);
  40. registerAuthenticationStrategy(this, JWTAuthenticationStrategy);
  41. // Set up the custom sequence
  42. this.sequence(MyAuthenticationSequence);
  43. // Set up default home page
  44. this.static('/', path.join(__dirname, '../public'));
  45. this.projectRoot = __dirname;
  46. // Customize @loopback/boot Booter Conventions here
  47. this.bootOptions = {
  48. controllers: {
  49. // Customize ControllerBooter Conventions here
  50. dirs: ['controllers'],
  51. extensions: ['.controller.js'],
  52. nested: true,
  53. },
  54. };
  55. }
  56. setUpBindings(): void {
  57. // Bind package.json to the application context
  58. this.bind(PackageKey).to(pkg);
  59. this.bind(TokenServiceBindings.TOKEN_SECRET).to(
  60. TokenServiceConstants.TOKEN_SECRET_VALUE,
  61. );
  62. this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(
  63. TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
  64. );
  65. this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);
  66. // // Bind bcrypt hash services
  67. this.bind(PasswordHasherBindings.ROUNDS).to(10);
  68. this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher);
  69. this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);
  70. }
  71. }

Running the Completed Application

To run the completed application, follow the instructions in theTry it out section.

For more information, please visitAuthentication Component.

Bugs/Feedback

Open an issue inloopback4-example-shoppingand we’ll take a look!

Contributions

Tests

Run npm test from the root folder.

Contributors

Seeall contributors.

License

MIT