lorenzoli
lorenzoli

Reputation: 55

Loopback4 - Authentication JWT Service error when binding

I have a problem with loopback4, especially when I try to create Custom Authentication UserService.

I've tried to define my own JWTStrategy and JWTService but I have same error.

I follow the docs (https://loopback.io/doc/en/lb4/JWT-authentication-extension.html#stability-%EF%B8%8Fexperimental%EF%B8%8F) but return an error when I run my API

Argument of type 'typeof MyUserService' is not assignable to parameter of type 'Constructor<UserService<User, Credentials>>'.
  Construct signature return types 'MyUserService' and 'UserService<User, Credentials>' are incompatible

This is my elements

PostgreSQL database => users table

CREATE TABLE public.users (
    username CHARACTER VARYING(10) NOT NULL UNIQUE,
    password CHARACTER VARYING(255) NOT NULL,
    PRIMARY KEY (username)
);

user.model.ts

import {Entity, model, property} from '@loopback/repository';

@model({settings: {strict: false}})
export class MyUser extends Entity {
  @property({
    type: 'string',
    id: true,
    generated: false,
    required: true,
  })
  username: string;

  @property({
    type: 'string',
    required: true,
  })
  password: string;
  
  [prop: string]: any;
  constructor(data?: Partial<MyUser>) {
    super(data);
  }
}

export interface UserRelations {
  // describe navigational properties here
}

export type UserWithRelations = MyUser & UserRelations;

user.repository.ts

import {DefaultCrudRepository} from '@loopback/repository';
import {MyUser, UserRelations} from '../models';
import {DbDataSource} from '../datasources';
import {inject} from '@loopback/core';

export class MyUserRepository extends DefaultCrudRepository<
  MyUser,
  typeof MyUser.prototype.username,
  UserRelations
> {
  constructor(
    @inject('datasources.db') dataSource: DbDataSource,
  ) {
    super(MyUser, dataSource);
  }
}

user.service.ts

import {UserService} from '@loopback/authentication';
import {repository} from '@loopback/repository';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {compare} from 'bcryptjs';
import {MyUser} from '../models';
import {MyUserRepository} from '../repositories';

export type Credentials = {
    username: string;
    password: string;
}

export class MyUserService implements UserService<MyUser, Credentials> {
    constructor(
        @repository(MyUserRepository) public userRepository: MyUserRepository
    ) {}

    async verifyCredentials(credentials: Credentials): Promise<MyUser> {
        const foundUser = await this.userRepository.findOne({
            where: {
                username: credentials.username
            }
        })

        if (!foundUser) throw new HttpErrors['NotFound']('Invalid creds');

        const isMatch = await compare(credentials.password, foundUser.password);
        if (!isMatch) throw new HttpErrors.Unauthorized('Password not match');

        return foundUser;
    }

    convertToUserProfile(user: MyUser): UserProfile {
        return {
            [securityId]: user.username,
            username: user.username
        }
    }
}

and my application.ts file

import {BootMixin} from '@loopback/boot';
import {ApplicationConfig} from '@loopback/core';
import {
  RestExplorerBindings,
  RestExplorerComponent,
} from '@loopback/rest-explorer';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {ServiceMixin} from '@loopback/service-proxy';
import path from 'path';
import {MySequence} from './sequence';
import {
  AuthenticationComponent,
} from '@loopback/authentication';
import {
  JWTAuthenticationComponent,
  UserServiceBindings
} from '@loopback/authentication-jwt';
import {
  DbDataSource
} from './datasources';
import {MyUserService} from './services';
import {MyUserRepository} from './repositories';

export {ApplicationConfig};

export class PsqlTodoApplication extends BootMixin(
  ServiceMixin(RepositoryMixin(RestApplication)),
) {
  constructor(options: ApplicationConfig = {}) {
    super(options);

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

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

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

    this.component(AuthenticationComponent);
    this.component(JWTAuthenticationComponent);
    this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME);
    this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService); // <= error is here!
    this.bind(UserServiceBindings.USER_REPOSITORY).toClass(MyUserRepository);

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

How can I create my own Credentials with username/password and not reuse Credentials from @loopback/authentication-jwt?

Upvotes: 1

Views: 578

Answers (1)

Zenul_Abidin
Zenul_Abidin

Reputation: 829

These kind of errors happen when your custom UserService type has member variables of different types from its corresponding interface, or member functions with different signatures - including return types - are different from the interface. Also this can happen with any custom implementation and its corresponding interface.

Also, services you write, including custom JwtService classes, are bound in the application.ts file like this:

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

Obviously this binding is for the token service, but this applies to any other built-in loopback service.

This discussion might also be of interest.

Upvotes: 0

Related Questions