I'm following the official NestJS documentation. Currently, I'm trying to implement the authentication step with Passport strategy. I did every step, as the documentation says, but I got stuck where I need to generate the JWT with the jwtService.sign() method.
The error, that I'm getting is:
ERROR [ExceptionsHandler] secretOrPrivateKey must have a value`.
Here are the code snippets:
AuthModule:
#Module({
imports: [
UserModule,
PassportModule,
User,
TypeOrmModule.forFeature([User]),
JwtModule.register({
secret: 'somerandomsecret',
signOptions: { expiresIn: '60s' }
})
],
providers: [AuthService, LocalStrategy, UserService],
exports: [AuthService]
})
export class AuthModule {}
AuthService
#Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService
){}
async validateUser(email: string, pass: string): Promise<any> {
const user = await this.userService.findByEmail(email);
const isMatch = await comparePasswords(pass, user.password);
if( user && isMatch) {
const { password, ...result } = user;
return result;
}
return null;
}
async signIn(user: any) {
const payload = { username: user.email, sub: user.id };
return this.jwtService.sign(payload)
}
}
And inside the User controller, I'm calling the method signIn from the AuthService.
UserController
import {
Body,
Controller,
Post,
HttpException,
HttpStatus,
Request,
UseGuards,
Bind,
} from "#nestjs/common";
import { UserService } from "./user.service";
import { SignUpDataValidation } from "./user.validation";
import { hashPassword } from "../../utils/hash-password";
import { AuthGuard } from '#nestjs/passport';
import { AuthService } from '../auth/auth.servers';
import { LocalAuthGuard } from '../auth/local-auth.guard';
#Controller("user")
export class UserController {
constructor(
private userService: UserService,
private authService: AuthService
) {}
#UseGuards(LocalAuthGuard)
#Post("/signin")
#Bind(Request())
async signIn(req) {
return this.authService.signIn(req.user)
}
}
UserModule
import { Module } from "#nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { TypeOrmModule } from "#nestjs/typeorm";
import { User } from "src/modules/user/user.entity";
import { AuthService } from '../auth/auth.servers';
import { JwtService } from '#nestjs/jwt';
#Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService, AuthService, JwtService],
})
export class UserModule {}
LocalStrategy
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.servers';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({usernameField: 'email'});
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email,
password);
if(!user) {
throw new UnauthorizedException()
}
return user;
}
}
As you can see, I'm also using Guards, but I'm not going to send you that code to avoid confusion.
So, can anybody tell me, why I'm getting this ERROR? Am I missing something?
Here is the link to the particular NestJS documentation page with a feature that I'm trying to implement: https://docs.nestjs.com/security/authentication
Related
I have a users REST API built with Nestjs, it connects to a mongo atlas db and fetchs the users correctly, now i want to add a ldap verification, right now i have a docker container running ldap. I've followed the documentation for using passport and
paspport strategies but i haven't figured out how to implement a ldap strategy yet. How to implement the ldap strategy using guards and routing from a users module?
I have an auth folder like this
//auth.module.ts
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LdapStrategy } from './ldap.strategy';
#Module({
providers: [AuthService, LdapStrategy],
imports: [UsersModule, PassportModule],
})
export class AuthModule {}
//ldap.strategy.ts
import * as Strategy from 'passport-ldapauth';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { AuthService } from './auth.service';
const OPTS = {
server: {
url: 'ldap://localhost:389',
bindDN: 'cn=admin,dc=gamestack,dc=unal,dc=edu,dc=co',
bindCredentials: 'admin',
searchBase: 'ou=sa',
searchFilter: '(uid={{username}})',
},
};
#Injectable()
export class LdapStrategy extends PassportStrategy(Strategy, 'ldapauth') {
constructor(private authService: AuthService) {
super(OPTS);
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
ldap-auth.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LdapAuthGuard extends AuthGuard('ldapauth') {}
I created a custom passport for authentication, as described here: https://docs.nestjs.com/security/authentication.
My problem is a different behavior between importing the passport I created from a local folder versus installing it from a package. When I install it from a package, I get 500 error when providing wrong credentials (works fine with valid credentials), while getting 401 error when using it locally.
This is how I use it locally and it works:
import { Controller, Get, UseGuards } from '#nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '#nestjs/passport';
import { ApiKeyAuthGuard } from './auth/guards/api-key-auth.guard';
#Controller()
#UseGuards(AuthGuard('api-key'))
export class AppController {
constructor(
private readonly appService: AppService,
) {}
#Get()
getHello(){}
}
But when I import it from an installed package and I provide wrong credentials, I'm getting 500 error:
import { ApiKeyAuthGuard } from 'shared-auth-package';
api-key.strategy.js:
import { PassportStrategy } from '#nestjs/passport';
import {
fromAuthHeaderAsApiKey,
Strategy,
} from '../passports/passport-api-key/strategy';
import { InjectRepository } from '#nestjs/typeorm';
import { UnauthorizedException } from '#nestjs/common';
import { Repository } from 'typeorm';
import { TokenEntity } from '../../lib/entity/token.entity';
import { UserEntity } from '../../lib/entity/user.entity';
export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') {
constructor(
#InjectRepository(TokenEntity, process.env.mysql_connection_name)
private tokenRepository: Repository<TokenEntity>,
) {
super({
tokenFunc: fromAuthHeaderAsApiKey(),
passReqToCallback: false,
});
}
async validate(token: string): Promise<UserEntity> {
let user: UserEntity;
const tokenEntity = await this.tokenRepository
.createQueryBuilder('t')
.innerJoinAndSelect('t.user', 'u')
.where('t.token = :token', { token })
.getOne();
if (tokenEntity && tokenEntity.user_id && tokenEntity.validateToken()) {
user = tokenEntity.user;
}
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return user;
}
}
api-key-auth.guard.ts:
import { AuthGuard } from '#nestjs/passport';
export class ApiKeyAuthGuard extends AuthGuard('api-key') {}
I have a problem that seems to be not that uncommon, but the solutions that I found did not work in my project.
What I want to do is a simple authentication using passport as this tutorial suggests: https://docs.nestjs.com/techniques/authentication
I followed this tutorial all along and at first it worked. Later I decided to use the users E-Mail and password as authentication instead of a username. So I changed my variable names and parameters in the authentication process to email and that was the point where everything broke apart. Am I missing something here?
auth.module.ts
import {Module} from '#nestjs/common';
import {UsersModule} from "../users/users.module";
import {AuthService} from "./services/auth.service";
import {PassportModule} from "#nestjs/passport";
import {LocalStrategy} from "./strategies/local.strategy";
import {AuthController} from "./controllers/auth.controller";
import {JwtModule} from "#nestjs/jwt";
import {jwtConstants} from "./constants";
import {JwtStrategy} from "./strategies/jwt.strategy";
import {EncryptionModule} from "../encryption/encryption.module";
#Module({
imports: [
UsersModule,
EncryptionModule,
PassportModule.register({defaultStrategy: 'jwt'}),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: {
expiresIn: '30s'
}
})
],
providers: [
AuthService,
LocalStrategy,
JwtStrategy
],
controllers: [
AuthController
]
})
export class AuthModule {
}
controllers/auth.controller.ts
import {Controller, Get, Post, Request, UseGuards} from '#nestjs/common';
import {AuthService} from "../services/auth.service";
import {JwtAuthGuard} from "../guards/jwt-auth.guard";
import {LocalAuthGuard} from "../guards/local-auth.guard";
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {
}
#UseGuards(LocalAuthGuard)
#Post('login')
login(#Request() req) {
return this.authService.login(req.user);
}
#UseGuards(JwtAuthGuard)
#Get('profile')
getProfile(#Request() req) {
return req.user;
}
}
services/auth.service.ts
import {Injectable} from '#nestjs/common';
import {UsersService} from "../../users/services/users.service";
import {User} from "../../users/interfaces/user.interface";
import {JwtService} from "#nestjs/jwt";
import {JwtPayloadDto} from "../models/jwt-payload.dto";
import {EncryptionService} from "../../encryption/services/encryption.service";
#Injectable()
export class AuthService {
constructor(private usersService: UsersService,
private jwtService: JwtService,
private encryptionService: EncryptionService) {
}
async validateUser(email: string, pass: string): Promise<User | undefined> {
/**
* The findOne-method sends a database query
* to my mongodb via mongoose.
* I don't think it's necessary to post the UserService here, is it?
*/
const user: User = await this.usersService.findOne(email);
return this.encryptionService.compare(pass, user.password).then((result) => {
if (result) {
return user;
}
return undefined;
});
}
async login(user: User) {
const payload: JwtPayloadDto = {
email: user.email,
sub: user.id
}
return {
accessToken: this.jwtService.sign(payload)
};
}
}
strategies/local.strategy.ts
import {Injectable, UnauthorizedException} from "#nestjs/common";
import {PassportStrategy} from "#nestjs/passport";
import {Strategy} from "passport-local";
import {AuthService} from "../services/auth.service";
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
guards/local-auth.guard.ts
import {Injectable} from "#nestjs/common";
import {AuthGuard} from "#nestjs/passport";
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
}
According to this question I found out that the validate-methods signature has to have the same parameter names as the request payloads keys.
For debugging purposes I have put a console.log()-call on the first line of my validate-method in the strategies/local.strategy.ts but it seems as it does not get called at all.
Thanks for any answer in advance.
Have a good one!
for me, when create LocalStrategy, I passed {usernameField: 'email'} to ParentClass.
If you want to check user authenticate with custom column like 'email', try pass it.
my user.entity.ts:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ unique: true })
email: string;
#Column()
name: string;
}
my local.strategy.ts:
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<User> {
console.log(email, password); // it works
}
}
Well, I solved it myself. 5 hours of debugging wasted!
Turned out that somehow my Postman did not send the Content-Type header with the request. Restarting Postman fixed it.
Everyone,
I'm trying to setup my first NestJS application. It is backed by Serverless on AWS.
I created a simple Controller that has a Service as a dependency. When I hit the endpoint with my HTTP Client, the object that should contain the Service instance is undefined. I'm not able to make it work. Could you help?
handler.ts
import { Context, Handler } from 'aws-lambda';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './src/module';
import { Server } from 'http';
import { ExpressAdapter } from '#nestjs/platform-express';
import * as serverless from 'aws-serverless-express';
import * as express from 'express';
import {DB} from './src/libs/db';
let cachedServer: Server;
function bootstrapServer(): Promise<Server> {
const expressApp = express();
const adapter = new ExpressAdapter(expressApp);
return NestFactory.create(AppModule, adapter)
.then(app => app.enableCors())
.then(app => app.init())
.then(() => DB.connect())
.then(() => serverless.createServer(expressApp));
}
export const handle: Handler = (event: any, context: Context) => {
if (!cachedServer) {
bootstrapServer().then(server => {
cachedServer = server;
return serverless.proxy(server, event, context);
});
} else {
return serverless.proxy(cachedServer, event, context);
}
};
module.ts
import { Module } from '#nestjs/common';
import { EventController } from './event.controller';
import { EventService } from './event.service';
#Module({
controllers: [EventController],
providers: [EventService],
})
export class AppModule {}
event.service.ts
import { Injectable } from '#nestjs/common';
interface Event{}
#Injectable()
export class EventService {
create(event: Event) {
return {
id: Date.now()
}
}
}
event.controller.ts
import { Controller, Post, Body } from '#nestjs/common';
import { EventService } from './event.service';
interface Event { }
#Controller('event')
export class EventController {
constructor(private readonly eventService: EventService) { }
#Post()
async create(#Body() req)
{
this.eventService.create(req);
}
}
So this.eventService is always undefined. What is wrong with this implementation?
Maybe you are missing a line from tsconfig add this below:
"emitDecoratorMetadata": true
Credits to the God of Nestjs Mr. Kamil's reply:
https://stackoverflow.com/a/50121886/6301493
I am testing my authService. This is the full Test, yet Karma tells me, authService is undefined. I have plenty of Service which AuthService depends upon, but I provided and injected them all properly.
Error: Cannot resolve all parameters for 'AuthService'(BackendService, Store, LoggerService, undefined, ErrorService). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'AuthService' is decorated with Injectable. in /var/folders/zb/tpysrhsx7hbg1dnsn4gwtqq00000gn/T/8715f9a6c29e748f52c8f59e3e1daae3.browserify (line 34976)
authservice.spec.ts
import { provide } from "#angular/core";
import { AuthHttp } from "angular2-jwt";
import { HTTP_PROVIDERS, XHRBackend } from "#angular/http";
import { MockBackend } from "#angular/http/testing";
import {
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
} from "#angular/platform-browser-dynamic/testing";
import {
beforeEachProviders,
inject,
beforeEach,
it,
describe,
setBaseTestProviders
} from "#angular/core/testing";
import { Subject } from "rxjs/Subject";
import { AuthService } from "./auth.service";
import { BackendService } from "../../backend/backend.service";
import { ErrorService } from "../../error/error.service";
import { LoggerService } from "../../logger/logger.service";
import { NavService } from "../../nav/nav-service/nav.service";
import { Store } from "#ngrx/store";
import { TestComponentBuilder } from "#angular/compiler/testing";
import { ToastController, AlertController } from "ionic-angular";
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
describe("AuthService", () => {
let response = new Subject();
let tcb;
let authService;
let navService;
let backendService;
let errorService;
let store;
let loggerService;
class StubErrorService extends ErrorService {
constructor() {
super(null, null);
}
toast(title) {
console.error(title);
}
modal(title, subtitle) {
console.error(title, subtitle);
}
}
class StubBackendService extends BackendService {
}
class StubStore extends Store<any> {
}
class StubLoggerService extends LoggerService {
}
class StubNavService extends NavService {
}
// PROVIDE
beforeEachProviders(() => [
HTTP_PROVIDERS,
provide(AuthHttp, {
useValue: {
get: (url: string) => {
return response;
}
}
}),
AuthService,
TestComponentBuilder,
provide(ToastController, {useClass: null}),
provide(AlertController, {useClass: null}),
provide(ErrorService, {useClass: StubErrorService}),
provide(XHRBackend, {useClass: MockBackend}),
provide(BackendService, {useClass: StubBackendService}),
provide(Store, {useClass: StubStore}),
provide(LoggerService, {useClass: StubLoggerService}),
provide(NavService, {useClass: StubNavService})
]);
// INJECTS
beforeEach(inject([TestComponentBuilder, AuthService, ErrorService, BackendService, Store, LoggerService, NavService], (_tcb, as, es, bs, s, ls, ns) => {
tcb = _tcb;
authService = as;
navService = ns;
errorService = es;
store = s;
backendService = bs;
loggerService = ls;
}));
it("should test authservice", () => {
authService.logout();
});
});
I don't know if its relevant anymore, just to say I had an almost identical issue and I resolved it following the official docs on how to test services. Hope it helps!