@loopback/docs
Version:
Documentation files rendered at [https://loopback.io](https://loopback.io)
394 lines (337 loc) • 13.2 kB
Markdown
---
lang: en
title: 'Migrating Passport-based authentication'
keywords:
LoopBack 4.0, LoopBack 4, Node.js, TypeScript, OpenAPI, LoopBack 3, Migration
sidebar: lb4_sidebar
permalink: /doc/en/lb4/migration-auth-passport.html
---
{% include tip.html content="
Missing instructions for your LoopBack 3 use case? Please report a [Migration docs issue](https://github.com/strongloop/loopback-next/issues/new?labels=question,Migration,Docs&template=Migration_docs.md) on GitHub to let us know.
" %}
# Migrating Passport Strategies
This page is a guide to migrate LB3 apps that use
[Passport Strategies](http://www.passportjs.org/) for authentication use cases.
Before following this guide, please know more about the
[/authentication-passport](https://loopback.io/doc/en/lb4/Authentication-passport.html)
package.
## Differences between LoopBack 3 and LoopBack 4
In LoopBack 3, routes can be configured explicitly as authentication providers
using Express style passport strategies middleware. Also the
[LB3 passport component](https://github.com/strongloop/loopback-component-passport)
helped with implicit authentication configuration using json files. It had
built-in model classes to search users and persist user identities.
In LoopBack 4, authentication endpoints are configured in controllers and the
`` decorator tells which passport strategy to configure for that
API route. Also the
[/authentication-passport](https://loopback.io/doc/en/lb4/Authentication-passport.html)
package is necessary to bridge between passport strategies and the
authentication design of LB4.
## An example passport login app
To demonstrate how to implement passport strategies in LoopBack 4 and migrate
LB3 apps using
[loopback-component-passport](https://github.com/strongloop/loopback-component-passport),
a
[passport-login](https://github.com/strongloop/loopback-next/tree/master/examples/passport-login)
example app is now available.
This example is migrated from
[loopback-example-passport](https://github.com/strongloop/loopback-example-passport),
it demonstrates how to use the LoopBack 4 features (like ``
decorator, strategy providers, etc) with passport strategies. It includes OAuth2
strategies to interact with external OAuth providers like Facebook, Google, etc
as well as local and basic strategies.
Take a look at the test cases of the
[example app](https://github.com/strongloop/loopback-next/tree/master/examples/passport-login)
and the
[mock social app for testing](https://github.com/strongloop/loopback-next/tree/master/extensions/authentication-passport/src/__tests__/acceptance)
You can use this example to see how to:
- Log in or sign up into a LoopBack application using passport strategy modules
- Log in via external apps like Facebook or link those external profiles with a
LoopBack user (for example, a LoopBack user can have associated
Facebook/Google accounts to retrieve pictures).
- Use basic or local passport strategy modules
This guide is further divided into two sections:
- [How to migrate Non-OAuth2 strategies like basic, local, etc.](#Non-OAuth2-Strategies)
- [How to migrate OAuth2 strategies like Facebook, Google, etc.](#OAuth2-Strategies)
In each of these sections the following are explained:
A. Configuring Authentication Endpoints: Authentication/Login endpoints are
controller methods that validate user credentials and provide the caller with a
login session which is usually represented by an access token or a cookie.
B. Strategy Providers: In LoopBack4 passport strategies will have to be injected
into the authentication using provider classes.
## Non OAuth2 Strategies
- This section shows how to implement authentication schemes like basic, local,
etc using passport strategies
- These authentication schemes validate users immediately without the need for
redirection ie., authentication happens in a single phase in one
request-response cycle.
### Configuring Authentication Endpoints
You can configure the authentication endpoints with the following steps:
- declare the `` decorator before controller methods that needs
access control
- include the binded name of the passport strategy provider as the decorator
parameter
- the decorator tells LoopBack that the strategy returned by the provider must
be called to validate user credentials
```ts
('session')
('/whoAmI', {
responses: USER_PROFILE_RESPONSE,
})
whoAmI((SecurityBindings.USER) user: UserProfile): object {
/**
* controller returns back currently logged in user information
*/
return {
user: user.profile,
headers: Object.assign({}, this.req.headers),
};
}
```
```ts
('basic')
('/profiles')
async getExternalProfiles(
(SecurityBindings.USER) profile: UserProfile,
) {
const user = await this.userRepository.findById(
parseInt(profile[securityId]),
{
include: [
{
relation: 'profiles',
},
],
},
);
return user.profiles;
}
```
### Strategy Providers
- Create a provider class that instantiates a passport strategy and wraps it
with a strategy adapter. Please read on
[/authentication-passport](https://loopback.io/doc/en/lb4/Authentication-passport.html).
```ts
/**
* basic passport strategy
*/
(asAuthStrategy)
export class BasicStrategy implements AuthenticationStrategy {
name = 'basic';
passportstrategy: Strategy;
strategy: StrategyAdapter<User>;
constructor(
(UserRepository)
public userRepository: UserRepository,
) {
/**
* create a basic passport strategy with verify function to validate credentials
*/
this.passportstrategy = new Strategy(this.verify.bind(this));
/**
* wrap the passport strategy instance with an adapter to plugin to LoopBack authentication
*/
this.strategy = new StrategyAdapter(
this.passportstrategy,
this.name,
mapProfile.bind(this),
);
}
/**
* authenticate a request
* @param request
*/
async authenticate(request: Request): Promise<UserProfile | RedirectRoute> {
return this.strategy.authenticate(request);
}
}
```
- Passport strategies also require a `verify` function to validate user
credentials in the request. Include the `verify` function in the provider
class.
```ts
/**
* authenticate user with provided username and password
*
* @param username
* @param password
* @param done
*
* @returns User model
*/
verify(
username: string,
password: string,
done: (error: any, user?: any) => void,
): void {
this.userRepository
.find({
where: {
email: username,
}
})
.then((users: User[]) => {
const user = users[0];
if (!user.credentials || user.credentials.password !== password) {
return done(null, false);
}
// Authentication passed, return user profile
done(null, user);
})
.catch(err => {
done(err);
});
}
```
- Bind the strategy provider class to the application
```ts
export class UserApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
this.add(createBindingFromClass(BasicStrategy));
this.sequence(MySequence);
this.component(AuthenticationComponent);
}
}
```
## OAuth2 Strategies
- how to use passport strategies for OAuth2 authorization flow with external
social apps like Facebook.
- this involves redirecting to an external app and user entering credentials in
that app's login page.
- this usecase includes multiple phases of authentication.
### Configuring Authentication Endpoints
You can configure the authentication endpoints with the following steps:
- For OAuth2 authorizaton flow, we need authentication endpoints that
participate to get the user validated with an external system.
- This essentially means the `` decorator is used with different
sematics compared to the non-OAuth2 section above.
- Here the controller methods become small parts of the larger OAuth2 dialog.
- We ideally create a controller with two endpoints decorated with
`(`{passport-strategy-name}`)`
- One of the endpoints is for redirecting to the external provider app and the
other is for getting called back by the external app.
- Create a controller with authentication endpoints as in below example:
- A method to redirect to the third party app (method `loginToThirdParty` in
the below example)
- an endpoint for api clients to login via a third party app
- the passport strategy identifies this call as a redirection to third
party
- this endpoint redirects to the third party authorization url
- A method for the third Party app to callback
- this is the callback for the thirdparty app (method `thirdPartyCallBack`
in the below example)
- on successful user login the third party calls this endpoint with an
access code
- the passport OAuth2 strategy exchanges the code for an access token
- the passport OAuth2 strategy then calls the provided `verify()` function
with the access token
```ts
('oauth2-Facebook')
('/auth/thirdparty/Facebook')
/**
* Endpoint: '/auth/thirdparty/Facebook'
* an endpoint for api clients to login via FaceBook, redirects to FaceBook
*/
loginToThirdParty(
(AuthenticationBindings.AUTHENTICATION_REDIRECT_URL)
redirectUrl: string,
(AuthenticationBindings.AUTHENTICATION_REDIRECT_STATUS)
status: number,
(RestBindings.Http.RESPONSE)
response: Response,
) {
response.statusCode = status || 302;
response.setHeader('Location', redirectUrl);
response.end();
return response;
}
('oauth2-Facebook')
('/auth/thirdparty/Facebook/callback')
/**
* Endpoint: '/auth/thirdparty/Facebook/callback'
* an endpoint which serves as a oauth2 callback for FaceBook
* this endpoint sets the user profile in the session
*/
async thirdPartyCallBack(
(SecurityBindings.USER) user: UserProfile, // Profile from FaceBook
(RestBindings.Http.REQUEST) request: RequestWithSession,
(RestBindings.Http.RESPONSE) response: Response,
) {
const profile = {
...user.profile,
};
request.session.user = profile;
response.redirect('/auth/account');
return response;
}
```
- Once `thirdPartyCallBack` endpoint has the profile from the external
authentication, it can proceed in three (or more) different ways.
- It can create a browser session. This is the most popular one we use
everyday to login to an app using Facebook/Google credentials. This means
the client from there on would use the passport-session strategy to access
other Usecase endpoints in the LoopBack App (session strategy).
- it can return the original oauth token from the third-party to the web
client which can then call the external app's usecase endpoints using the
token as a Bearer (jwt strategy).
- it can create a new token with the same expiration time of the original
thirdparty token (the exp field in that token says it) and send that to the
web client (jwt strategy).
### Strategy Providers
- Create a provider class that instantiates a passport strategy and wraps it
with a strategy adapter. Please read on
[/authentication-passport](https://loopback.io/doc/en/lb4/Authentication-passport.html).
```ts
(
asAuthStrategy,
extensionFor(PassportAuthenticationBindings.OAUTH2_STRATEGY),
)
export class FaceBookOauth2Authorization implements AuthenticationStrategy {
name = 'oauth2-Facebook';
protected strategy: StrategyAdapter<User>;
passportstrategy: Strategy;
/**
* create an oauth2 strategy for Facebook
*/
constructor(
(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE)
public userService: UserIdentityService<Profile, User>,
('FacebookOAuth2Options')
public FacebookOptions: StrategyOption,
) {
this.passportstrategy = new Strategy(
FacebookOptions,
verifyFunctionFactory(userService).bind(this),
);
this.strategy = new StrategyAdapter(
this.passportstrategy,
this.name,
mapProfile.bind(this),
);
}
/**
* authenticate a request
* @param request
*/
async authenticate(request: Request): Promise<UserProfile | RedirectRoute> {
return this.strategy.authenticate(request);
}
}
```
- Bind the strategy provider class to the application
```ts
export class OAuth2LoginApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
this.add(createBindingFromClass(FaceBookOauth2Authorization));
this.add(createBindingFromClass(GoogleOauth2Authorization));
this.sequence(MySequence);
this.component(AuthenticationComponent);
}
}
```