Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/passport-login/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ party apps
- [Google](https://console.developers.google.com/project)
- [twitter](https://apps.twitter.com/) **Not yet implemented**

## Authentication using passport strategies as Express midddleware

Take a look at how to use passport strategies
[as Express middleware using interceptors](src/authentication-interceptors)

## Authentication using passport strategies as a step in Application Sequence

Take a look at how to use passport strategies
[by invoking independent of Express](src/authentication-strategies)

## Install the example locally

1. Run the `lb4 example` command to install `example-passport-login` repository:
Expand Down
14 changes: 0 additions & 14 deletions examples/passport-login/data/db.json

This file was deleted.

21 changes: 16 additions & 5 deletions examples/passport-login/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions examples/passport-login/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@loopback/boot": "^2.1.2",
"@loopback/context": "^3.6.0",
"@loopback/core": "^2.4.2",
"@loopback/express": "^1.0.0",
"@loopback/openapi-v3": "^3.3.0",
"@loopback/repository": "^2.3.0",
"@loopback/rest": "^3.3.2",
Expand All @@ -49,6 +50,7 @@
"@loopback/service-proxy": "^2.1.2",
"@types/jsonwebtoken": "8.3.9",
"@types/lodash": "^4.14.149",
"@types/passport": "^1.0.3",
"@types/passport-facebook": "^2.1.9",
"@types/passport-google-oauth": "^1.0.41",
"@types/passport-google-oauth2": "^0.1.3",
Expand All @@ -63,6 +65,7 @@
"jade": "^1.11.0",
"lodash": "^4.17.15",
"p-event": "^4.1.0",
"passport": "^0.4.1",
"passport-facebook": "^3.0.0",
"passport-google": "^0.3.0",
"passport-google-oauth2": "^0.2.0",
Expand Down
54 changes: 53 additions & 1 deletion examples/passport-login/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import {BootMixin} from '@loopback/boot';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {RestApplication, toInterceptor} from '@loopback/rest';
import {ServiceMixin} from '@loopback/service-proxy';
import {MySequence} from './sequence';
import {AuthenticationComponent} from '@loopback/authentication';
Expand All @@ -17,9 +17,24 @@ import {
SessionStrategy,
BasicStrategy,
} from './authentication-strategies';
import {
FacebookOauth,
GoogleOauth,
CustomOauth2,
FacebookOauth2ExpressMiddleware,
GoogleOauth2ExpressMiddleware,
CustomOauth2ExpressMiddleware,
} from './authentication-strategy-providers';
import {
SessionAuth,
FacebookOauthInterceptor,
GoogleOauthInterceptor,
CustomOauth2Interceptor,
} from './authentication-interceptors';
import {PassportUserIdentityService, UserServiceBindings} from './services';
import {ApplicationConfig, createBindingFromClass} from '@loopback/core';
import {CrudRestComponent} from '@loopback/rest-crud';
import passport from 'passport';

export class OAuth2LoginApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
Expand All @@ -35,6 +50,15 @@ export class OAuth2LoginApplication extends BootMixin(
this.component(AuthenticationComponent);
this.component(CrudRestComponent);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
passport.serializeUser(function (user: any, done) {
done(null, user);
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
passport.deserializeUser(function (user: any, done) {
done(null, user);
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can compose 5 interceptors into one (depending on #5343)

import {composeInterceptors} from '@loopback/context';
const authInterceptor = composeInterceptors(
  'passport-init-mw', // or use `toInterceptor(passport.initialize())` directly without binding it
  'passport-session-mw', // or use `toInterceptor(passport.session())` 
  'passport-facebook', 
  'passport-google', 
  'passport-oauth2');

// ...
@intercept(authInterceptor)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect, thank you

this.projectRoot = __dirname;
// Customize @loopback/boot Booter Conventions here
this.bootOptions = {
Expand All @@ -51,11 +75,39 @@ export class OAuth2LoginApplication extends BootMixin(
this.bind(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE).toClass(
PassportUserIdentityService,
);
// passport strategies
this.add(createBindingFromClass(FacebookOauth, {key: 'facebookStrategy'}));
this.add(createBindingFromClass(GoogleOauth, {key: 'googleStrategy'}));
this.add(createBindingFromClass(CustomOauth2, {key: 'oauth2Strategy'}));
// passport express middleware
this.add(
createBindingFromClass(FacebookOauth2ExpressMiddleware, {
key: 'facebookStrategyMiddleware',
}),
);
this.add(
createBindingFromClass(GoogleOauth2ExpressMiddleware, {
key: 'googleStrategyMiddleware',
}),
);
this.add(
createBindingFromClass(CustomOauth2ExpressMiddleware, {
key: 'oauth2StrategyMiddleware',
}),
);
// LoopBack 4 style authentication strategies
this.add(createBindingFromClass(LocalAuthStrategy));
this.add(createBindingFromClass(FaceBookOauth2Authorization));
this.add(createBindingFromClass(GoogleOauth2Authorization));
this.add(createBindingFromClass(Oauth2AuthStrategy));
this.add(createBindingFromClass(SessionStrategy));
this.add(createBindingFromClass(BasicStrategy));
// Express style middleware interceptors
this.bind('passport-init-mw').to(toInterceptor(passport.initialize()));
this.bind('passport-session-mw').to(toInterceptor(passport.session()));
this.bind('passport-facebook').toProvider(FacebookOauthInterceptor);
this.bind('passport-google').toProvider(GoogleOauthInterceptor);
this.bind('passport-oauth2').toProvider(CustomOauth2Interceptor);
this.bind('set-session-user').toProvider(SessionAuth);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to make these interceptor providers singleton too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-passport-login
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
inject,
Provider,
Interceptor,
InvocationContext,
Next,
} from '@loopback/core';
import {
RestBindings,
RequestContext,
toInterceptor,
ExpressRequestHandler,
} from '@loopback/rest';

export class FacebookOauthInterceptor implements Provider<Interceptor> {
constructor(
@inject('facebookStrategyMiddleware')
public facebookStrategy: ExpressRequestHandler,
) {}

value() {
return async (invocationCtx: InvocationContext, next: Next) => {
const requestCtx = invocationCtx.getSync<RequestContext>(
RestBindings.Http.CONTEXT,
);
const request = requestCtx.request;
if (request.query['oauth2-provider-name'] === 'facebook') {
return toInterceptor(this.facebookStrategy)(invocationCtx, next);
}
return next();
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-passport-login
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
inject,
Provider,
Interceptor,
InvocationContext,
Next,
} from '@loopback/core';
import {
RestBindings,
RequestContext,
toInterceptor,
ExpressRequestHandler,
} from '@loopback/rest';

export class GoogleOauthInterceptor implements Provider<Interceptor> {
constructor(
@inject('googleStrategyMiddleware')
public googleStrategy: ExpressRequestHandler,
) {}

value() {
return async (invocationCtx: InvocationContext, next: Next) => {
const requestCtx = invocationCtx.getSync<RequestContext>(
RestBindings.Http.CONTEXT,
);
const request = requestCtx.request;
if (request.query['oauth2-provider-name'] === 'google') {
return toInterceptor(this.googleStrategy)(invocationCtx, next);
}
return next();
};
}
}
10 changes: 10 additions & 0 deletions examples/passport-login/src/authentication-interceptors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-passport-login
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './types';
export * from './oauth2.interceptor';
export * from './session.interceptor';
export * from './facebook.interceptor';
export * from './google.interceptor';
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-passport-login
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
inject,
Provider,
InvocationContext,
Next,
Interceptor,
} from '@loopback/core';
import {
RestBindings,
RequestContext,
toInterceptor,
ExpressRequestHandler,
} from '@loopback/rest';

export class CustomOauth2Interceptor implements Provider<Interceptor> {
constructor(
@inject('oauth2StrategyMiddleware')
public oauth2Strategy: ExpressRequestHandler,
) {}

value() {
return async (invocationCtx: InvocationContext, next: Next) => {
const requestCtx = invocationCtx.getSync<RequestContext>(
RestBindings.Http.CONTEXT,
);
const request = requestCtx.request;
if (request.query['oauth2-provider-name'] === 'oauth2') {
return toInterceptor(this.oauth2Strategy)(invocationCtx, next);
}
return next();
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-passport-login
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Provider, Interceptor, InvocationContext, Next} from '@loopback/core';
import {RestBindings, RequestContext} from '@loopback/rest';
import {SecurityBindings} from '@loopback/security';
import {mapProfile} from '../authentication-strategies/types';
import {User} from '../models';

export class SessionAuth implements Provider<Interceptor> {
constructor() {}

value() {
return async (invocationCtx: InvocationContext, next: Next) => {
const requestCtx = invocationCtx.getSync<RequestContext>(
RestBindings.Http.CONTEXT,
);
const request = requestCtx.request;
if (request.user) {
const user: User = request.user as User;
requestCtx.bind(SecurityBindings.USER).to(mapProfile(user));
}
return next();
};
}
}
31 changes: 31 additions & 0 deletions examples/passport-login/src/authentication-interceptors/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-passport-login
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {composeInterceptors, intercept} from '@loopback/core';

/**
* Name: OAuth2InterceptExpressMiddleware
* Type: DECORATOR
*
* This method uses the @intercept decorator to intercept incoming requests with a series of passport strategies.
* It composes an middleware interceptor chain with the following interceptor keys:
* 'passport-init-mw',
* 'passport-session-mw',
* 'passport-facebook',
* 'passport-google',
* 'passport-oauth2'
*/
export function OAuth2InterceptExpressMiddleware() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the name can be simpler as oauth2. This way, the decorator will be @oauth2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, but I have to simplify as well as be clear this is via interceptors middleware chain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try simplifying

return intercept(
composeInterceptors(
'passport-init-mw',
'passport-session-mw',
'passport-facebook',
'passport-oauth2',
'passport-google',
'set-session-user',
),
);
}
Loading