loopbackio / loopback-next

LoopBack makes it easy to build modern API applications that require complex integrations.
https://loopback.io
Other
4.97k stars 1.07k forks source link

refresh token #6427

Closed touagueni closed 4 years ago

touagueni commented 4 years ago

Hi everyone!

I am using npm 6.14.8.

I have a working implementation of authentication from the loopback4-example-shopping. when trying to add the refresh token capability, I am facing the following:

Unhandled error in POST /refresh-login: 500 ResolutionError: The key 'services.authentication.jwt.refresh.tokenservice' is not bound to any value in context RequestContext-fFKtBWS_Tw2PmEVjTh7oKQ-3 (context: RequestContext-fFKtBWS_Tw2PmEVjTh7oKQ-3, binding: services.authentication.jwt.refresh.tokenservice, resolutionPath: controllers.AuthController --> @AuthController.constructor[6]) at RequestContext.getValueOrPromise (C:\avokap\backend\node_modules\@loopback\context\src\context.ts:810:13) at C:\avokap\backend\node_modules\@loopback\context\src\resolver.ts:159:20 at C:\avokap\backend\node_modules\@loopback\context\src\resolution-session.ts:157:13 at tryCatchFinally (C:\avokap\backend\node_modules\@loopback\context\src\value-promise.ts:222:14) at Object.tryWithFinally (C:\avokap\backend\node_modules\@loopback\context\src\value-promise.ts:197:10) at Function.runWithInjection (C:\avokap\backend\node_modules\@loopback\context\src\resolution-session.ts:156:12) at resolve (C:\avokap\backend\node_modules\@loopback\context\src\resolver.ts:143:38) at C:\avokap\backend\node_modules\@loopback\context\src\resolver.ts:246:12 at Object.resolveList (C:\avokap\backend\node_modules\@loopback\context\src\value-promise.ts:169:28) at resolveInjectedArguments (C:\avokap\backend\node_modules\@loopback\context\src\resolver.ts:224:10) at Object.instantiateClass (C:\avokap\backend\node_modules\@loopback\context\src\resolver.ts:62:25) at C:\avokap\backend\node_modules\@loopback\context\src\binding.ts:748:29 at Binding._getValue (C:\avokap\backend\node_modules\@loopback\context\src\binding.ts:604:14) at C:\avokap\backend\node_modules\@loopback\context\src\binding.ts:479:23 at C:\avokap\backend\node_modules\@loopback\context\src\resolution-session.ts:122:13 at tryCatchFinally (C:\avokap\backend\node_modules\@loopback\context\src\value-promise.ts:222:14)

Please, can anyone help?

Many Thanks in advance

Tofik

raymondfeng commented 4 years ago

Did you call app.component('JWTAuthenticationComponent') in src/application.ts?

https://github.com/strongloop/loopback-next/blob/70741829678b5a0dc9c277a12674725c8a2efb1d/extensions/authentication-jwt/src/jwt-authentication-component.ts#L51

raymondfeng commented 4 years ago

Please also make sure you use the latest version of loopback4-example-shopping as we recently updated it to use newest LoopBack versions.

touagueni commented 4 years ago

Hi Raymond,

Please, see below the content of the src/application.ts file:

import {authenticate, AuthenticationComponent} from '@loopback/authentication';
import {AuthorizationComponent} from '@loopback/authorization';
import {BootMixin} from '@loopback/boot';
import {
  ApplicationConfig,
  BindingKey,
  createBindingFromClass
} from '@loopback/core';
import {
  model,
  property,
  RepositoryMixin,
  SchemaMigrationOptions
} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {
  RestExplorerBindings,
  RestExplorerComponent
} from '@loopback/rest-explorer';
import {ServiceMixin} from '@loopback/service-proxy';
import path from 'path';
import {JWTAuthenticationStrategy} from './authentication-strategies/jwt-strategy';
import {MongoDataSource} from './datasources/mongo.datasource';
import {
  PasswordHasherBindings, RefreshTokenServiceBindings, TokenServiceBindings,
  TokenServiceConstants,
  UserServiceBindings
} from './keys';
import {User} from './models';
import {MyAuthenticationSequence} from './sequence';
import {BcryptHasher} from './services/hash.password.bcryptjs';
import {JWTService} from './services/jwt-service';
import {MyUserService} from './services/user.service';
import YAML = require('yaml');

export {ApplicationConfig};

/**
 * Information from package.json
 */
export interface PackageInfo {
  name: string;
  version: string;
  description: string;
}
export const PackageKey = BindingKey.create<PackageInfo>('application.package');

const pkg: PackageInfo = require('../package.json');

@model()
export class NewUser extends User {
  @property({
    type: 'string',
    required: true,
  })
  password: string;
}
@authenticate.skip()

export class AvokapApplication extends BootMixin(
  ServiceMixin(RepositoryMixin(RestApplication)),
) {
  constructor(options?: ApplicationConfig) {
    super(options);

    this.setUpBindings();

    // Bind authentication component related elements
    this.component(AuthenticationComponent);
    this.component(AuthorizationComponent);
    /****** */

    // authentication
    this.add(createBindingFromClass(JWTAuthenticationStrategy));

    // Set up the custom sequence
    this.sequence(MyAuthenticationSequence);

    // Set up default home page
    this.static('/', path.join(__dirname, '../public'));

    // Customize @loopback/rest-explorer configuration here
    this.bind(RestExplorerBindings.CONFIG).to({
      path: '/explorer',
    });
    this.component(RestExplorerComponent);

    this.projectRoot = __dirname;
    // Customize @loopback/boot Booter Conventions here
    this.bootOptions = {
      controllers: {
        // Customize ControllerBooter Conventions here
        dirs: ['controllers'],
        extensions: ['.controller.js'],
        nested: true,
      },
    };
  }

  setUpBindings(): void {
    // Bind package.json to the application context
    this.bind(PackageKey).to(pkg);

    this.bind(TokenServiceBindings.TOKEN_SECRET).to(
      TokenServiceConstants.TOKEN_SECRET_VALUE,
    );

    this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(
      TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
    );

    this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);

    // // Bind bcrypt hash services
    this.bind(PasswordHasherBindings.ROUNDS).to(10);
    this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher);

    this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);

    /*********************************************************** */
    // Bind datasource
    this.dataSource(MongoDataSource, UserServiceBindings.DATASOURCE_NAME);
    //Bind datasource for refreshtoken table
    this.dataSource(MongoDataSource, RefreshTokenServiceBindings.DATASOURCE_NAME);

    /************************************************************ */

  }

  // Unfortunately, TypeScript does not allow overriding methods inherited
  // from mapped types. https://github.com/microsoft/TypeScript/issues/38496
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  async start(): Promise<void> {
    // Use `databaseSeeding` flag to control if products/users should be pre
    // populated into the database. Its value is default to `true`.
    if (this.options.databaseSeeding !== false) {
      await this.migrateSchema();
    }
    return super.start();
  }

  async migrateSchema(options?: SchemaMigrationOptions): Promise<void> {
    await super.migrateSchema(options);

    // // Pre-populate products
    // const productRepo = await this.getRepository(ProductRepository);
    // await productRepo.deleteAll();
    // const productsDir = path.join(__dirname, '../fixtures/products');
    // const productFiles = fs.readdirSync(productsDir);

    // for (const file of productFiles) {
    //   if (file.endsWith('.yml')) {
    //     const productFile = path.join(productsDir, file);
    //     const yamlString = fs.readFileSync(productFile, 'utf8');
    //     const product = YAML.parse(yamlString);
    //     await productRepo.create(product);
    //   }
    // }

    // // Pre-populate users
    // const passwordHasher = await this.get(
    //   PasswordHasherBindings.PASSWORD_HASHER,
    // );
    // const userRepo = await this.getRepository(UserRepository);
    // await userRepo.deleteAll();
    // const usersDir = path.join(__dirname, '../fixtures/users');
    // const userFiles = fs.readdirSync(usersDir);

    // for (const file of userFiles) {
    //   if (file.endsWith('.yml')) {
    //     const userFile = path.join(usersDir, file);
    //     const yamlString = YAML.parse(fs.readFileSync(userFile, 'utf8'));
    //     const input = new NewUser(yamlString);
    //     const password = await passwordHasher.hashPassword(input.password);
    //     input.password = password;
    //     const user = await userRepo.create(_.omit(input, 'password'));

    //     await userRepo.userCredentials(user.id).create({password});
    //   }
    // }

    // // Delete existing shopping carts
    // const cartRepo = await this.getRepository(ShoppingCartRepository);
    // await cartRepo.deleteAll();

    // // Delete existing orders
    // const orderRepo = await this.getRepository(OrderRepository);
    // await orderRepo.deleteAll();
  }
}
raymondfeng commented 4 years ago

I don't see something like https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts#L77.

raymondfeng commented 4 years ago

You'll have to add this.bind(RefreshTokenServiceBindings.REFRESH_TOKEN_SERVICE).toClass(...) if you register all bindings without using the jwt component.

touagueni commented 4 years ago

Raymond I only had to use the application.ts from the shpping application provided example. it works now, much appreciated. cheers T