Passport.js
Passport is authentication middleware for Node.js.
Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.
Installation
Before using the Passport, we need to install the Passport.js and the Passport-local.
npm install --save passport
Override AuthenticatedMiddleware
The annotation @Authenticated()
use the AuthenticatedMiddleware
to check the authentication strategy.
So, create a new file in your middlewares directory and past this code:
import {OverrideMiddleware, AuthenticatedMiddleware} from "@tsed/common";
import {Forbidden} from "ts-httpexceptions";
@OverrideMiddleware(AuthenticatedMiddleware)
export class MyAuthenticatedMiddleware implements IMiddleware {
public use(@EndpointInfo() endpoint: EndpointMetadata,
@Request() request: Express.Request,
@Next() next: Express.NextFunction) { // next is optional here
// options given to the @Authenticated decorator
const options = endpoint.get(AuthenticatedMiddleware) || {};
// options => {role: 'admin'}
if (!request.isAuthenticated()) { // passport.js
throw new Forbidden("Forbidden")
}
next();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Local strategy
Now, we need to expose some routes to enable the login, the signup and the logout. To do that, we'll use the passport-local strategy and we create a passport service and a passport controller.
The PassportLocalService
In the service directory, we'll create the PassportLocalServices.ts
and write this code:
import * as Passport from "passport";
import {Strategy} from "passport-local";
import {Service, BeforeRoutesInit, AfterRoutesInit} from "@tsed/common";
import {UserService} from "./UserService"; // other service that manage the users account
@Service()
export class PassportLocalService implements BeforeRoutesInit, AfterRoutesInit {
constructor(private serverSettings: ServerSettingsService,
private userService: UserService,
@Inject(ExpressApplication) private expressApplication: ExpressApplication) {
}
$beforeRoutesInit() {
const options: any = this.serverSettings.get("passport") || {} as any;
const {userProperty, pauseStream} = options; // options stored with ServerSettings
this.expressApplication.use(Passport.initialize({userProperty}));
this.expressApplication.use(Passport.session({pauseStream}));
}
$afterRoutesInit() {
this.initializeSignup();
this.initializeLogin();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
We use the hook service licecycle to autoloading some actions when the server start. See the service lifecycle for more informations.
Passport controller
We'll need to prepare some routes. To work it, the Passport need 3 routes:
/login
, email and password will be sent in the body request,/signup
, email, password and optional information will be sent in the body request,/logout
.
So create the PassportCtrl
in the controllers directory and put this code:
"use strict";
import * as Express from "express";
import {BodyParams, Controller, Get, Post, Req, Required, Res} from "@tsed/common";
@Controller("/passport")
export class PassportCtrl {
@Post("/login")
async login(@Required() @BodyParams("email") email: string,
@Required() @BodyParams("password") password: string,
@Req() request: Express.Request,
@Res() response: Express.Response) {
}
@Post("/signup")
async signup(@Required() @BodyParams("email") email: string,
@Required() @BodyParams("password") password: string,
@Required() @BodyParams("firstName") firstName: string,
@Required() @BodyParams("lastName") lastName: string,
@Req() request: Express.Request,
@Res() response: Express.Response) {
}
@Get("/logout")
public logout(@Req() request: Express.Request): string {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Signup
We will use Passport to register the signup action with the LocalStrategy. Passport will call back the handler when the
the PassportCtrl call the Passport.authenticate('signup')
method.
In the PassportCtrl, we need to implement the Passport.authenticate('signup')
like this:
import * as Express from "express";
import * as Passport from "passport";
import {BodyParams, Controller, Get, Post, Req, Required, Res} from "@tsed/common";
import {IUser} from "../../interfaces/User";
@Controller("/passport")
export class PassportCtrl {
@Post("/signup")
async signup(@Required() @BodyParams("email") email: string,
@Required() @BodyParams("password") password: string,
@Required() @BodyParams("firstName") firstName: string,
@Required() @BodyParams("lastName") lastName: string,
@Req() request: Express.Request,
@Res() response: Express.Response) {
return new Promise((resolve, reject) => {
Passport.authenticate("signup", (err, user: IUser) => {
if (err) {
return reject(err);
}
if (!user) {
return reject(!!err);
}
request.logIn(user, (err) => {
if (err) {
return reject(err);
}
resolve(user);
});
})(request, response, () => {
});
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Now, when a Request is sent to the signup route, Passport will emit a signup event.
In your PassportLocalService, we able to implement the Passport Local strategy on the signup event.
import * as Passport from "passport";
import {Strategy} from "passport-local";
import {Service, BeforeRoutesInit, AfterRoutesInit} from "@tsed/common";
import {BadRequest} from "ts-httpexceptions";
@Service()
export class PassportLocalService implements BeforeRoutesInit, AfterRoutesInit {
//...
public initializeSignup() {
Passport
.use("signup", new Strategy({
// by default, local strategy uses username and password, we will override with email
usernameField: "email",
passwordField: "password",
passReqToCallback: true // allows us to pass back the entire request to the callback
},
(req, email, password, done) => {
const {firstName, lastName} = req.body;
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(() => {
this.signup({
firstName,
lastName,
email,
password
})
.then((user) => done(null, user))
.catch((err) => done(err));
});
}));
}
/**
*
* @param user
* @returns {Promise<any>}
*/
async signup(user: IUser) {
const exists = await this.usersService.findByEmail(user.email);
if (exists) { //User exists
throw new BadRequest("Email is already registered");
}
// Promise
// Create new User
return await this.usersService.create(<any>{
email: user.email,
password: user.password,
firstName: user.firstName,
lastName: user.lastName
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
We'll not implement the userService. We'll assume that it'll do what is expected.
Login
Implement login is the same principle that the signup. In the controller we need to use the Passport.authenticate("login")
method to emit the login
event to each Passport Strategy.
import * as Express from "express";
import * as Passport from "passport";
import {BodyParams, Controller, Get, Post, Req, Required, Res} from "@tsed/common";
import {IUser} from "../../interfaces/User";
@Controller("/passport")
export class PassportCtrl {
@Post("/login")
async login(@Required() @BodyParams("email") email: string,
@Required() @BodyParams("password") password: string,
@Req() request: Express.Request,
@Res() response: Express.Response) {
return new Promise<IUser>((resolve, reject) => {
Passport
.authenticate("login", (err, user: IUser) => {
if (err) {
reject(err);
}
request.logIn(user, (err) => {
if (err) {
reject(err);
} else {
resolve(user);
}
});
})(request, response, () => {
});
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
In your service we can do that:
import * as Passport from "passport";
import {Strategy} from "passport-local";
import {Service, BeforeRoutesInit, AfterRoutesInit} from "@tsed/common";
import {BadRequest, NotFound} from "ts-httpexceptions";
@Service()
export class PassportLocalService implements BeforeRoutesInit, AfterRoutesInit {
//...
public initializeLogin() {
Passport.use("login", new Strategy({
// by default, local strategy uses username and password, we will override with email
usernameField: "email",
passwordField: "password",
passReqToCallback: true // allows us to pass back the entire request to the callback
}, (req, email, password, done) => {
this.login(email, password)
.then((user) => done(null, user))
.catch((err) => done(err));
}));
}
async login(email: string, password: string): Promise<IUser> {
const user = await this.usersService.findByCredential(email, password);
if (user) {
return user;
}
throw new NotFound("User not found");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Logout
Logout is very short, just place this code in the PassportCtrl and it's done:
import * as Express from "express";
import {BodyParams, Controller, Get, Post, Req, Required, Res} from "@tsed/common";
import {IUser} from "../../interfaces/User";
@Controller("/passport")
export class PassportCtrl {
@Get("/logout")
public logout(@Req() request: Express.Request): string {
request.logout();
return "Disconnected";
}
}
2
3
4
5
6
7
8
9
10
11
12
TIP
You can find a working example on Passport.js here.