-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: try passport login example with express middleware #5339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be able to make these interceptor providers singleton too.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| }; | ||
| } | ||
| } |
| 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(); | ||
| }; | ||
| } | ||
| } |
| 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() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the name can be simpler as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
| ), | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect, thank you