docs/site/migration/auth/passport.md
{% include tip.html content=" Missing instructions for your LoopBack 3 use case? Please report a Migration docs issue on GitHub to let us know. " %}
This page is a guide to migrate LB3 apps that use Passport Strategies for authentication use cases. Before following this guide, please know more about the @loopback/authentication-passport package.
In LoopBack 3, routes can be configured explicitly as authentication providers using Express style passport strategies middleware. Also the LB3 passport component 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
@authenticate decorator tells which passport strategy to configure for that
API route. Also the
@loopback/authentication-passport
package is necessary to bridge between passport strategies and the
authentication design of LB4.
To demonstrate how to implement passport strategies in LoopBack 4 and migrate LB3 apps using loopback-component-passport, a passport-login example app is now available.
This example is migrated from
loopback-example-passport,
it demonstrates how to use the LoopBack 4 features (like @authenticate
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 and the mock social app for testing
You can use this example to see how to:
This guide is further divided into two sections:
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.
You can configure the authentication endpoints with the following steps:
@authenticate decorator before controller methods that needs
access control @authenticate('session')
@get('/whoAmI', {
responses: USER_PROFILE_RESPONSE,
})
whoAmI(@inject(SecurityBindings.USER) user: UserProfile): object {
/**
* controller returns back currently logged in user information
*/
return {
user: user.profile,
headers: Object.assign({}, this.req.headers),
};
}
@authenticate('basic')
@get('/profiles')
async getExternalProfiles(
@inject(SecurityBindings.USER) profile: UserProfile,
) {
const user = await this.userRepository.findById(
parseInt(profile[securityId]),
{
include: [
{
relation: 'profiles',
},
],
},
);
return user.profiles;
}
/**
* basic passport strategy
*/
@injectable(asAuthStrategy)
export class BasicStrategy implements AuthenticationStrategy {
name = 'basic';
passportstrategy: Strategy;
strategy: StrategyAdapter<User>;
constructor(
@repository(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);
}
}
verify function to validate user
credentials in the request. Include the verify function in the provider
class. /**
* 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);
});
}
export class UserApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
this.add(createBindingFromClass(BasicStrategy));
this.sequence(MySequence);
this.component(AuthenticationComponent);
}
}
You can configure the authentication endpoints with the following steps:
@authenticate decorator is used with different
semantics compared to the non-OAuth2 section above.@authenticate({passport-strategy-name})A method to redirect to the third party app (method loginToThirdParty in
the below example)
A method for the third Party app to callback
thirdPartyCallBack
in the below example)verify() function
with the access token @authenticate('oauth2-Facebook')
@get('/auth/thirdparty/Facebook')
/**
* Endpoint: '/auth/thirdparty/Facebook'
* an endpoint for api clients to login via FaceBook, redirects to FaceBook
*/
loginToThirdParty(
@inject(AuthenticationBindings.AUTHENTICATION_REDIRECT_URL)
redirectUrl: string,
@inject(AuthenticationBindings.AUTHENTICATION_REDIRECT_STATUS)
status: number,
@inject(RestBindings.Http.RESPONSE)
response: Response,
) {
response.statusCode = status || 302;
response.setHeader('Location', redirectUrl);
response.end();
return response;
}
@authenticate('oauth2-Facebook')
@get('/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(
@inject(SecurityBindings.USER) user: UserProfile, // Profile from FaceBook
@inject(RestBindings.Http.REQUEST) request: RequestWithSession,
@inject(RestBindings.Http.RESPONSE) response: Response,
) {
const profile = {
...user.profile,
};
request.session.user = profile;
response.redirect('/auth/account');
return response;
}
thirdPartyCallBack endpoint has the profile from the external
authentication, it can proceed in three (or more) different ways.
@injectable(
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(
@inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE)
public userService: UserIdentityService<Profile, User>,
@inject('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);
}
}
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);
}
}