From 5402e375f0687716f3f0627b3b586b882ebea453 Mon Sep 17 00:00:00 2001 From: DEEPAK RAJAMOHAN Date: Thu, 30 Apr 2020 19:45:28 -0700 Subject: [PATCH] feat: try mw with passport login example --- examples/passport-login/README.md | 10 ++++ examples/passport-login/data/db.json | 14 ----- examples/passport-login/package-lock.json | 21 ++++++-- examples/passport-login/package.json | 3 ++ examples/passport-login/src/application.ts | 54 ++++++++++++++++++- .../facebook.interceptor.ts | 38 +++++++++++++ .../google.interceptor.ts | 38 +++++++++++++ .../src/authentication-interceptors/index.ts | 10 ++++ .../oauth2.interceptor.ts | 38 +++++++++++++ .../session.interceptor.ts | 28 ++++++++++ .../src/authentication-interceptors/types.ts | 31 +++++++++++ .../src/authentication-strategies/facebook.ts | 25 +++------ .../src/authentication-strategies/google.ts | 23 ++------ .../src/authentication-strategies/oauth2.ts | 36 ++----------- .../facebook.express-mw.ts | 24 +++++++++ .../facebook.ts | 34 ++++++++++++ .../google.express-mw.ts | 24 +++++++++ .../google.ts | 34 ++++++++++++ .../index.ts | 11 ++++ .../oauth2.express-mw.ts | 24 +++++++++ .../oauth2.ts | 44 +++++++++++++++ .../src/controllers/oauth2.controller.ts | 12 ++++- examples/passport-login/src/index.ts | 2 +- examples/passport-login/tsconfig.json | 3 ++ 24 files changed, 491 insertions(+), 90 deletions(-) delete mode 100644 examples/passport-login/data/db.json create mode 100644 examples/passport-login/src/authentication-interceptors/facebook.interceptor.ts create mode 100644 examples/passport-login/src/authentication-interceptors/google.interceptor.ts create mode 100644 examples/passport-login/src/authentication-interceptors/index.ts create mode 100644 examples/passport-login/src/authentication-interceptors/oauth2.interceptor.ts create mode 100644 examples/passport-login/src/authentication-interceptors/session.interceptor.ts create mode 100644 examples/passport-login/src/authentication-interceptors/types.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/facebook.express-mw.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/facebook.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/google.express-mw.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/google.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/index.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/oauth2.express-mw.ts create mode 100644 examples/passport-login/src/authentication-strategy-providers/oauth2.ts diff --git a/examples/passport-login/README.md b/examples/passport-login/README.md index 134bf5230ae9..fde2868d97dd 100644 --- a/examples/passport-login/README.md +++ b/examples/passport-login/README.md @@ -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: diff --git a/examples/passport-login/data/db.json b/examples/passport-login/data/db.json deleted file mode 100644 index 22fa883bcb82..000000000000 --- a/examples/passport-login/data/db.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "ids": { - "User": 1, - "UserIdentity": 1, - "UserCredentials": 1 - }, - "models": { - "User": { - }, - "UserIdentity": { - }, - "UserCredentials": {} - } -} \ No newline at end of file diff --git a/examples/passport-login/package-lock.json b/examples/passport-login/package-lock.json index 9c46247d9458..43d143835cc7 100644 --- a/examples/passport-login/package-lock.json +++ b/examples/passport-login/package-lock.json @@ -1655,12 +1655,12 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "passport": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.1.18.tgz", - "integrity": "sha1-yCZEedy2QUytu2Z1LRKzfgtlJaE=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", "requires": { - "pause": "0.0.1", - "pkginfo": "0.2.x" + "passport-strategy": "1.x.x", + "pause": "0.0.1" } }, "passport-facebook": { @@ -1724,6 +1724,17 @@ "openid": "0.5.x", "passport": "~0.1.3", "pkginfo": "0.2.x" + }, + "dependencies": { + "passport": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.1.18.tgz", + "integrity": "sha1-yCZEedy2QUytu2Z1LRKzfgtlJaE=", + "requires": { + "pause": "0.0.1", + "pkginfo": "0.2.x" + } + } } }, "passport-strategy": { diff --git a/examples/passport-login/package.json b/examples/passport-login/package.json index 8a109894d22a..052e9a6f58ed 100644 --- a/examples/passport-login/package.json +++ b/examples/passport-login/package.json @@ -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", @@ -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", @@ -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", diff --git a/examples/passport-login/src/application.ts b/examples/passport-login/src/application.ts index deb090487de2..b09c9c466cea 100644 --- a/examples/passport-login/src/application.ts +++ b/examples/passport-login/src/application.ts @@ -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'; @@ -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)), @@ -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); + }); + this.projectRoot = __dirname; // Customize @loopback/boot Booter Conventions here this.bootOptions = { @@ -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); } } diff --git a/examples/passport-login/src/authentication-interceptors/facebook.interceptor.ts b/examples/passport-login/src/authentication-interceptors/facebook.interceptor.ts new file mode 100644 index 000000000000..0fbe30474269 --- /dev/null +++ b/examples/passport-login/src/authentication-interceptors/facebook.interceptor.ts @@ -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 { + constructor( + @inject('facebookStrategyMiddleware') + public facebookStrategy: ExpressRequestHandler, + ) {} + + value() { + return async (invocationCtx: InvocationContext, next: Next) => { + const requestCtx = invocationCtx.getSync( + RestBindings.Http.CONTEXT, + ); + const request = requestCtx.request; + if (request.query['oauth2-provider-name'] === 'facebook') { + return toInterceptor(this.facebookStrategy)(invocationCtx, next); + } + return next(); + }; + } +} diff --git a/examples/passport-login/src/authentication-interceptors/google.interceptor.ts b/examples/passport-login/src/authentication-interceptors/google.interceptor.ts new file mode 100644 index 000000000000..90ba08b7d5cf --- /dev/null +++ b/examples/passport-login/src/authentication-interceptors/google.interceptor.ts @@ -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 { + constructor( + @inject('googleStrategyMiddleware') + public googleStrategy: ExpressRequestHandler, + ) {} + + value() { + return async (invocationCtx: InvocationContext, next: Next) => { + const requestCtx = invocationCtx.getSync( + RestBindings.Http.CONTEXT, + ); + const request = requestCtx.request; + if (request.query['oauth2-provider-name'] === 'google') { + return toInterceptor(this.googleStrategy)(invocationCtx, next); + } + return next(); + }; + } +} diff --git a/examples/passport-login/src/authentication-interceptors/index.ts b/examples/passport-login/src/authentication-interceptors/index.ts new file mode 100644 index 000000000000..dc81709b18b4 --- /dev/null +++ b/examples/passport-login/src/authentication-interceptors/index.ts @@ -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'; diff --git a/examples/passport-login/src/authentication-interceptors/oauth2.interceptor.ts b/examples/passport-login/src/authentication-interceptors/oauth2.interceptor.ts new file mode 100644 index 000000000000..ae505e8d0569 --- /dev/null +++ b/examples/passport-login/src/authentication-interceptors/oauth2.interceptor.ts @@ -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 { + constructor( + @inject('oauth2StrategyMiddleware') + public oauth2Strategy: ExpressRequestHandler, + ) {} + + value() { + return async (invocationCtx: InvocationContext, next: Next) => { + const requestCtx = invocationCtx.getSync( + RestBindings.Http.CONTEXT, + ); + const request = requestCtx.request; + if (request.query['oauth2-provider-name'] === 'oauth2') { + return toInterceptor(this.oauth2Strategy)(invocationCtx, next); + } + return next(); + }; + } +} diff --git a/examples/passport-login/src/authentication-interceptors/session.interceptor.ts b/examples/passport-login/src/authentication-interceptors/session.interceptor.ts new file mode 100644 index 000000000000..22b001acce72 --- /dev/null +++ b/examples/passport-login/src/authentication-interceptors/session.interceptor.ts @@ -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 { + constructor() {} + + value() { + return async (invocationCtx: InvocationContext, next: Next) => { + const requestCtx = invocationCtx.getSync( + 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(); + }; + } +} diff --git a/examples/passport-login/src/authentication-interceptors/types.ts b/examples/passport-login/src/authentication-interceptors/types.ts new file mode 100644 index 000000000000..42c35a77e639 --- /dev/null +++ b/examples/passport-login/src/authentication-interceptors/types.ts @@ -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() { + return intercept( + composeInterceptors( + 'passport-init-mw', + 'passport-session-mw', + 'passport-facebook', + 'passport-oauth2', + 'passport-google', + 'set-session-user', + ), + ); +} diff --git a/examples/passport-login/src/authentication-strategies/facebook.ts b/examples/passport-login/src/authentication-strategies/facebook.ts index 368d3f971f59..e198b3d58274 100644 --- a/examples/passport-login/src/authentication-strategies/facebook.ts +++ b/examples/passport-login/src/authentication-strategies/facebook.ts @@ -3,22 +3,16 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { - asAuthStrategy, - AuthenticationStrategy, - UserIdentityService, -} from '@loopback/authentication'; +import {asAuthStrategy, AuthenticationStrategy} from '@loopback/authentication'; import {StrategyAdapter} from '@loopback/authentication-passport'; -import {Profile} from 'passport'; -import {Strategy, StrategyOption} from 'passport-facebook'; +import {Strategy} from 'passport-facebook'; import {bind, inject} from '@loopback/context'; -import {UserServiceBindings} from '../services'; import {extensionFor} from '@loopback/core'; import {UserProfile} from '@loopback/security'; import {User} from '../models'; import {Request, RedirectRoute} from '@loopback/rest'; import {PassportAuthenticationBindings} from './types'; -import {verifyFunctionFactory, mapProfile} from './types'; +import {mapProfile} from './types'; @bind( asAuthStrategy, @@ -27,23 +21,16 @@ import {verifyFunctionFactory, mapProfile} from './types'; export class FaceBookOauth2Authorization implements AuthenticationStrategy { name = 'oauth2-facebook'; protected strategy: StrategyAdapter; - passportstrategy: Strategy; /** * create an oauth2 strategy for facebook */ constructor( - @inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE) - public userService: UserIdentityService, - @inject('facebookOAuth2Options') - public facebookOptions: StrategyOption, + @inject('facebookStrategy') + public passportStrategy: Strategy, ) { - this.passportstrategy = new Strategy( - facebookOptions, - verifyFunctionFactory(userService).bind(this), - ); this.strategy = new StrategyAdapter( - this.passportstrategy, + this.passportStrategy, this.name, mapProfile.bind(this), ); diff --git a/examples/passport-login/src/authentication-strategies/google.ts b/examples/passport-login/src/authentication-strategies/google.ts index 4bdd98784d7d..34e656b70e43 100644 --- a/examples/passport-login/src/authentication-strategies/google.ts +++ b/examples/passport-login/src/authentication-strategies/google.ts @@ -3,22 +3,16 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { - asAuthStrategy, - AuthenticationStrategy, - UserIdentityService, -} from '@loopback/authentication'; +import {asAuthStrategy, AuthenticationStrategy} from '@loopback/authentication'; import {StrategyAdapter} from '@loopback/authentication-passport'; -import {Profile} from 'passport'; -import {Strategy, StrategyOptions} from 'passport-google-oauth2'; +import {Strategy} from 'passport-google-oauth2'; import {bind, inject} from '@loopback/context'; -import {UserServiceBindings} from '../services'; import {extensionFor} from '@loopback/core'; import {UserProfile} from '@loopback/security'; import {User} from '../models'; import {Request, RedirectRoute} from '@loopback/rest'; import {PassportAuthenticationBindings} from './types'; -import {verifyFunctionFactory, mapProfile} from './types'; +import {mapProfile} from './types'; @bind( asAuthStrategy, @@ -26,22 +20,15 @@ import {verifyFunctionFactory, mapProfile} from './types'; ) export class GoogleOauth2Authorization implements AuthenticationStrategy { name = 'oauth2-google'; - passportstrategy: Strategy; protected strategy: StrategyAdapter; /** * create an oauth2 strategy for google */ constructor( - @inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE) - public userService: UserIdentityService, - @inject('googleOAuth2Options') - public googleOptions: StrategyOptions, + @inject('googleStrategy') + public passportstrategy: Strategy, ) { - this.passportstrategy = new Strategy( - googleOptions, - verifyFunctionFactory(userService).bind(this), - ); this.strategy = new StrategyAdapter( this.passportstrategy, this.name, diff --git a/examples/passport-login/src/authentication-strategies/oauth2.ts b/examples/passport-login/src/authentication-strategies/oauth2.ts index 922d75027bc9..a52b9817ed49 100644 --- a/examples/passport-login/src/authentication-strategies/oauth2.ts +++ b/examples/passport-login/src/authentication-strategies/oauth2.ts @@ -3,30 +3,18 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { - AuthenticationStrategy, - UserIdentityService, - asAuthStrategy, -} from '@loopback/authentication'; +import {AuthenticationStrategy, asAuthStrategy} from '@loopback/authentication'; import {StrategyAdapter} from '@loopback/authentication-passport'; -import {Profile} from 'passport'; -import {Strategy, StrategyOptions} from 'passport-oauth2'; +import {Strategy} from 'passport-oauth2'; import {Request, RedirectRoute} from '@loopback/rest'; import {UserProfile} from '@loopback/security'; import {User} from '../models'; -import {UserServiceBindings} from '../services'; import {inject, bind, extensions, Getter} from '@loopback/core'; -import { - verifyFunctionFactory, - PassportAuthenticationBindings, - profileFunction, - mapProfile, -} from './types'; +import {PassportAuthenticationBindings, mapProfile} from './types'; @bind(asAuthStrategy) export class Oauth2AuthStrategy implements AuthenticationStrategy { name = 'oauth2'; - passportstrategy: Strategy; protected strategy: StrategyAdapter; /** @@ -39,23 +27,9 @@ export class Oauth2AuthStrategy implements AuthenticationStrategy { */ @extensions(PassportAuthenticationBindings.OAUTH2_STRATEGY) private getStrategies: Getter, - @inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE) - public userService: UserIdentityService, - @inject('customOAuth2Options') - public oauth2Options: StrategyOptions, - @inject('authentication.oauth2.profile.function', {optional: true}) - public profileFn: profileFunction, + @inject('oauth2Strategy') + public passportstrategy: Strategy, ) { - /** - * Create a oauth2 strategy instance for a custom provider implementation - */ - this.passportstrategy = new Strategy( - oauth2Options, - verifyFunctionFactory(userService).bind(this), - ); - if (profileFn) { - this.passportstrategy.userProfile = profileFn; - } this.strategy = new StrategyAdapter( this.passportstrategy, this.name, diff --git a/examples/passport-login/src/authentication-strategy-providers/facebook.express-mw.ts b/examples/passport-login/src/authentication-strategy-providers/facebook.express-mw.ts new file mode 100644 index 000000000000..6efff072fc1c --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/facebook.express-mw.ts @@ -0,0 +1,24 @@ +// 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 + +const passport = require('passport'); +import {inject, Provider, bind, BindingScope} from '@loopback/core'; +import {Strategy as FacebookStrategy} from 'passport-facebook'; +import {ExpressRequestHandler} from '@loopback/rest'; + +@bind.provider({scope: BindingScope.SINGLETON}) +export class FacebookOauth2ExpressMiddleware + implements Provider { + constructor( + @inject('facebookStrategy') + public facebookStrategy: FacebookStrategy, + ) { + passport.use(this.facebookStrategy); + } + + value() { + return passport.authenticate('facebook'); + } +} diff --git a/examples/passport-login/src/authentication-strategy-providers/facebook.ts b/examples/passport-login/src/authentication-strategy-providers/facebook.ts new file mode 100644 index 000000000000..d2d672176b0c --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/facebook.ts @@ -0,0 +1,34 @@ +// 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 {Profile} from 'passport'; +import {UserIdentityService} from '@loopback/authentication'; +import {User} from '../models'; +import {StrategyOption} from 'passport-facebook'; +import {inject, Provider, bind, BindingScope} from '@loopback/core'; +import {UserServiceBindings} from '../services'; +import {Strategy as FacebookStrategy} from 'passport-facebook'; +import {verifyFunctionFactory} from '../authentication-strategies/types'; + +@bind.provider({scope: BindingScope.SINGLETON}) +export class FacebookOauth implements Provider { + strategy: FacebookStrategy; + + constructor( + @inject('facebookOAuth2Options') + public facebookOptions: StrategyOption, + @inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE) + public userService: UserIdentityService, + ) { + this.strategy = new FacebookStrategy( + this.facebookOptions, + verifyFunctionFactory(this.userService), + ); + } + + value() { + return this.strategy; + } +} diff --git a/examples/passport-login/src/authentication-strategy-providers/google.express-mw.ts b/examples/passport-login/src/authentication-strategy-providers/google.express-mw.ts new file mode 100644 index 000000000000..fdc1d38b036d --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/google.express-mw.ts @@ -0,0 +1,24 @@ +// 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 + +const passport = require('passport'); +import {inject, Provider, bind, BindingScope} from '@loopback/core'; +import {Strategy as GoogleStrategy} from 'passport-google-oauth2'; +import {ExpressRequestHandler} from '@loopback/rest'; + +@bind.provider({scope: BindingScope.SINGLETON}) +export class GoogleOauth2ExpressMiddleware + implements Provider { + constructor( + @inject('googleStrategy') + public strategy: GoogleStrategy, + ) { + passport.use(this.strategy); + } + + value() { + return passport.authenticate('google'); + } +} diff --git a/examples/passport-login/src/authentication-strategy-providers/google.ts b/examples/passport-login/src/authentication-strategy-providers/google.ts new file mode 100644 index 000000000000..e3fbc16914ae --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/google.ts @@ -0,0 +1,34 @@ +// 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 {Profile} from 'passport'; +import {UserIdentityService} from '@loopback/authentication'; +import {User} from '../models'; +import {StrategyOption} from 'passport-facebook'; +import {inject, Provider, bind, BindingScope} from '@loopback/core'; +import {UserServiceBindings} from '../services'; +import {Strategy as GoogleStrategy} from 'passport-google-oauth2'; +import {verifyFunctionFactory} from '../authentication-strategies/types'; + +@bind.provider({scope: BindingScope.SINGLETON}) +export class GoogleOauth implements Provider { + strategy: GoogleStrategy; + + constructor( + @inject('googleOAuth2Options') + public oauth2Options: StrategyOption, + @inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE) + public userService: UserIdentityService, + ) { + this.strategy = new GoogleStrategy( + this.oauth2Options, + verifyFunctionFactory(this.userService), + ); + } + + value() { + return this.strategy; + } +} diff --git a/examples/passport-login/src/authentication-strategy-providers/index.ts b/examples/passport-login/src/authentication-strategy-providers/index.ts new file mode 100644 index 000000000000..24098f2dca72 --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/index.ts @@ -0,0 +1,11 @@ +// 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 './facebook'; +export * from './google'; +export * from './oauth2'; +export * from './facebook.express-mw'; +export * from './google.express-mw'; +export * from './oauth2.express-mw'; diff --git a/examples/passport-login/src/authentication-strategy-providers/oauth2.express-mw.ts b/examples/passport-login/src/authentication-strategy-providers/oauth2.express-mw.ts new file mode 100644 index 000000000000..6da4ee3c906f --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/oauth2.express-mw.ts @@ -0,0 +1,24 @@ +// 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 + +const passport = require('passport'); +import {inject, Provider, BindingScope, bind} from '@loopback/core'; +import {Strategy as OAuth2Strategy} from 'passport-oauth2'; +import {ExpressRequestHandler} from '@loopback/rest'; + +@bind.provider({scope: BindingScope.SINGLETON}) +export class CustomOauth2ExpressMiddleware + implements Provider { + constructor( + @inject('oauth2Strategy') + public oauth2Strategy: OAuth2Strategy, + ) { + passport.use(this.oauth2Strategy); + } + + value() { + return passport.authenticate('oauth2'); + } +} diff --git a/examples/passport-login/src/authentication-strategy-providers/oauth2.ts b/examples/passport-login/src/authentication-strategy-providers/oauth2.ts new file mode 100644 index 000000000000..9ae941df0b94 --- /dev/null +++ b/examples/passport-login/src/authentication-strategy-providers/oauth2.ts @@ -0,0 +1,44 @@ +// 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 {Profile} from 'passport'; +import {UserIdentityService} from '@loopback/authentication'; +import {User} from '../models'; +import {inject, Provider, BindingScope, bind} from '@loopback/core'; +import {UserServiceBindings} from '../services'; +import { + Strategy as OAuth2Strategy, + StrategyOptions as OAuth2StrategyOptions, +} from 'passport-oauth2'; +import { + verifyFunctionFactory, + profileFunction, +} from '../authentication-strategies/types'; + +@bind.provider({scope: BindingScope.SINGLETON}) +export class CustomOauth2 implements Provider { + strategy: OAuth2Strategy; + + constructor( + @inject('customOAuth2Options') + public oauth2Options: OAuth2StrategyOptions, + @inject('authentication.oauth2.profile.function', {optional: true}) + public profileFn: profileFunction, + @inject(UserServiceBindings.PASSPORT_USER_IDENTITY_SERVICE) + public userService: UserIdentityService, + ) { + if (profileFn) { + OAuth2Strategy.prototype.userProfile = profileFn; + } + this.strategy = new OAuth2Strategy( + this.oauth2Options, + verifyFunctionFactory(this.userService), + ); + } + + value() { + return this.strategy; + } +} diff --git a/examples/passport-login/src/controllers/oauth2.controller.ts b/examples/passport-login/src/controllers/oauth2.controller.ts index 26a955aab3f0..533783a9f7e6 100644 --- a/examples/passport-login/src/controllers/oauth2.controller.ts +++ b/examples/passport-login/src/controllers/oauth2.controller.ts @@ -13,9 +13,15 @@ import { import {authenticate, AuthenticationBindings} from '@loopback/authentication'; import {inject} from '@loopback/core'; import {SecurityBindings, UserProfile} from '@loopback/security'; +import {OAuth2InterceptExpressMiddleware} from '../authentication-interceptors/types'; /** * Login controller for third party oauth provider + * + * This controller demonstrates using passport strategies both as express middleware and as an independent strategy + * + * The method loginToThirdParty uses the @authenticate decorator to plugin passport strategies independently + * The method thirdPartyCallBack uses the passport strategies as express middleware */ export class Oauth2Controller { constructor() {} @@ -23,6 +29,8 @@ export class Oauth2Controller { @authenticate('oauth2') @get('/auth/thirdparty/{provider}') /** + * This method uses the @authenticate decorator to plugin passport strategies independently + * * Endpoint: '/auth/thirdparty/{provider}' * an endpoint for api clients to login via a third party app, redirects to third party app */ @@ -41,9 +49,11 @@ export class Oauth2Controller { return response; } - @authenticate('oauth2') + @OAuth2InterceptExpressMiddleware() @get('/auth/thirdparty/{provider}/callback') /** + * This method uses the passport strategies as express middleware + * * Endpoint: '/auth/thirdparty/{provider}/callback' * an endpoint which serves as a oauth2 callback for the thirdparty app * this endpoint sets the user profile in the session diff --git a/examples/passport-login/src/index.ts b/examples/passport-login/src/index.ts index 090a408e1942..89a39159516d 100644 --- a/examples/passport-login/src/index.ts +++ b/examples/passport-login/src/index.ts @@ -87,7 +87,7 @@ export async function main() { } const server: ExpressServer = await startApplication( oauth2Providers, - '../data/db.json', + process.env.DB_BKP_FILE_PATH, // eg: export DB_BKP_FILE_PATH=../data/db.json ); console.log(`Server is running at ${server.url}`); } diff --git a/examples/passport-login/tsconfig.json b/examples/passport-login/tsconfig.json index ec89e209689a..b57a17c97d8f 100644 --- a/examples/passport-login/tsconfig.json +++ b/examples/passport-login/tsconfig.json @@ -33,6 +33,9 @@ { "path": "../../packages/core/tsconfig.json" }, + { + "path": "../../packages/express/tsconfig.json" + }, { "path": "../../packages/openapi-v3/tsconfig.json" },