Loopback-next: Inject repository in auth provider

Created on 10 Oct 2018  路  12Comments  路  Source: strongloop/loopback-next

_Related: #1826_

I am trying to validate if the token passed in the header of the request is valid, for this I need to find in the database.

I'm trying to inject the repository, but the same gets as undefined. How should I proceed?

import { Provider, inject, ValueOrPromise } from '@loopback/context';
import { Strategy } from 'passport';
import { AuthenticationBindings, AuthenticationMetadata, UserProfile, } from '@loopback/authentication';
import { BasicStrategy } from 'passport-http';
import { AccesstokenRepository } from '../repositories';
import { repository } from '@loopback/repository';

export class MyAuthStrategyProvider implements Provider<Strategy | undefined> {

  constructor(
    @repository(AccesstokenRepository) public accesstokenRepository: AccesstokenRepository,
    @inject(AuthenticationBindings.METADATA) private metadata: AuthenticationMetadata,
  ) { }

  value(): ValueOrPromise<Strategy | undefined> {
    // The function was not decorated, so we shouldn't attempt authentication
    if (!this.metadata) {
      return undefined;
    }
    return new BasicStrategy({
      realm: '',
      passReqToCallback: true
    }, this.verify2);
  }

  verify2(
    req: any,
    username: string,
    password: string,
    done: (error: any, user?: any) => void,
  ) {
    //console.log('req >', req.headers)
    const headers = req.headers

    if (headers.authorization) {
      // here is undefined!!!!!
      console.log('accesstokenRepository >', this.accesstokenRepository);
    } else {
      return done(null, false)
    }
  }
}

It's in my controller and it works:
export class AccesstokenController { constructor( @repository(AccesstokenRepository) public accesstokenRepository: AccesstokenRepository, ) { }

Authentication question

Most helpful comment

@ericalves I think the root cause is the following:

return new BasicStrategy({
      realm: '',
      passReqToCallback: true
    }, this.verify2);

Please note the this.verify2 lose this when it's invoked.

Can you try the following fix?

return new BasicStrategy({
      realm: '',
      passReqToCallback: true
    }, this.verify2.bind(this));

All 12 comments

You probably don't have the repository bound to the context when the MyAuthStrategyProvider is instantiated. The AccesstokenRepository is bound by @loopback/boot during app.boot().

@ericalves can you try to inject a repository getter instead of the repository class? I don't remember the implementation details of @loopback/authentication, but if the Strategy is instantiated early in application lifecycle (for example from the @authenticate decorator at the time when Controller classes are defined by JS/TS runtime), then getter should solve the problem.

export class MyAuthStrategyProvider implements Provider<Strategy | undefined> {

  constructor(
    @repository.getter(AccesstokenRepository)
    public getAccesstokenRepository: Getter<AccesstokenRepository>,
    @inject(AuthenticationBindings.METADATA) private metadata: AuthenticationMetadata,
  ) { }

  // ...
  verify2(
    req: any,
    username: string,
    password: string,
    done: (error: any, user?: any) => void,
  ) {
    const repo = this.getAccesstokenRepository();
    // etc.
  }
}

I have the same problem as @ericalves. When I have tried your last solution, the problem has not disappeared. I have checked the value of 'this' variable and I have this value: "BasicStrategy { success: [Function], fail: [Function], error: [Function] }". I think that the context inside of 'Verify' function is not the same of the main class 'MyAuthStrategyProvider'.

If one of you can create a sample repo to reproduce the problem, we can help troubleshoot.

I have a model (login.model.ts) with the structure data of users and the repository class (login.repository.ts) with this definition:
```import {DefaultCrudRepository, juggler} from '@loopback/repository';
import {Login} from '../models';
import {inject} from '@loopback/core';

export class LoginRepository extends DefaultCrudRepository {
constructor(@inject('datasources.db') dataSource: juggler.DataSource) {
super(Login, dataSource);
}
}


In the strategy provider class i have this:
```import {repository} from '@loopback/repository';
import {Provider, inject, ValueOrPromise} from '@loopback/context';
import {Strategy} from 'passport';
import {LoginRepository} from '../repositories/login.repository';
import {AuthenticationBindings, AuthenticationMetadata, UserProfile} from '@loopback/authentication';
import {BasicStrategy} from 'passport-http';

export class MyAuthStrategyProvider implements Provider<Strategy | undefined> {
constructor(
    @repository(LoginRepository) protected loginRepo: LoginRepository,
    @inject(AuthenticationBindings.METADATA) private metadata: AuthenticationMetadata,
  ) { }

  value(): ValueOrPromise<Strategy | undefined> {
    // The function was not decorated, so we shouldn't attempt authentication
    if (!this.metadata) {
      return undefined;
    }
    const name = this.metadata.strategy;
    if (name === 'BasicStrategy') {
      return new BasicStrategy({
        passReqToCallback: true
      }, this.verify);
    } else {
      return Promise.reject(`The strategy ${name} is not available.`);
    }
  }

  verify(
    req: any,
    username: string,
    password: string,
    cb: (err: Error | null, user?: UserProfile | false) => void,
  ) {
    console.log(this)
    // find user by name & password
    // call cb(null, false) when user not found
    // call cb(null, user) when user is authenticated
  }
}

I follow the instructions defined in the readme of '@loopback/authentication': https://github.com/strongloop/loopback-next/tree/master/packages/authentication
The problem is when I need to use de LoginRepository object inside of verify function, this value is undefined.

@lolocr Thank you for the information. I would appreciate if you can create a github repo with a LB4 app and your code to show the problem. It would be very helpful for me to check out the repo and debug it.

You can find the repo here: https://github.com/lolocr/lb4auth
Thank you for all

@lolocr I'll take a look

@ericalves I think the root cause is the following:

return new BasicStrategy({
      realm: '',
      passReqToCallback: true
    }, this.verify2);

Please note the this.verify2 lose this when it's invoked.

Can you try the following fix?

return new BasicStrategy({
      realm: '',
      passReqToCallback: true
    }, this.verify2.bind(this));

BTW, I confirmed in debugger that LB4 dependency injection of the repository works correctly in the RequestContext.

It works perfectly!
Thank you for your help.

@raymondfeng thank you man

Was this page helpful?
0 / 5 - 0 ratings