Created
January 29, 2020 14:44
-
-
Save bseib/da68438e3a55cede78698362e80001ad to your computer and use it in GitHub Desktop.
loopback 4 authentication authorization example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {BootMixin} from '@loopback/boot'; | |
import {ApplicationConfig} from '@loopback/core'; | |
import { | |
AuthenticationComponent, | |
registerAuthenticationStrategy, | |
AuthenticationBindings, | |
} from '@loopback/authentication'; | |
import { | |
// AuthorizationOptions, | |
// AuthorizationDecision, | |
AuthorizationComponent, | |
AuthorizationTags, | |
} from '@loopback/authorization'; | |
import { | |
RestExplorerBindings, | |
RestExplorerComponent, | |
} from '@loopback/rest-explorer'; | |
import {RepositoryMixin} from '@loopback/repository'; | |
import {RestApplication} from '@loopback/rest'; | |
import {ServiceMixin} from '@loopback/service-proxy'; | |
import path from 'path'; | |
import {MySequence} from './sequence'; | |
import {MyAuthenticationStrategy} from './MyAuthenticationStrategy'; | |
import {MyAuthenticationStrategyBindings} from './keys'; | |
import {MyMachineUserService} from './services'; | |
import {MyAuthorizationProvider} from './MyAuthorizationProvider'; | |
export class HelloLoopbackApplication extends BootMixin( | |
ServiceMixin(RepositoryMixin(RestApplication)), | |
) { | |
constructor(options: ApplicationConfig = {}) { | |
super(options); | |
// Set up authentication. The real work is added to MySequence. | |
this.component(AuthenticationComponent); | |
registerAuthenticationStrategy(this, MyAuthenticationStrategy); | |
this.bind(MyAuthenticationStrategyBindings.MACHINE_USER_SERVICE).toClass( | |
MyMachineUserService, | |
); | |
this.bind(MyAuthenticationStrategyBindings.DEFAULT_OPTIONS).to({ | |
public: false, | |
}); | |
// turn on authentication by default for all endpoints | |
// https://loopback.io/doc/en/lb4/Loopback-component-authentication.html#default-authentication-metadata | |
this.configure(AuthenticationBindings.COMPONENT).to({ | |
defaultMetadata: {strategy: 'my'}, | |
}); | |
// setup authorization | |
this.component(AuthorizationComponent); | |
this.bind('authorizationProviders.my-authorization-provider') | |
.toProvider(MyAuthorizationProvider) | |
.tag(AuthorizationTags.AUTHORIZER); | |
// Set up the custom sequence | |
this.sequence(MySequence); | |
// Set up default home page | |
this.static('/', path.join(__dirname, '../public')); | |
// Customize @loopback/rest-explorer configuration here | |
this.bind(RestExplorerBindings.CONFIG).to({ | |
path: '/explorer', | |
}); | |
this.component(RestExplorerComponent); | |
this.projectRoot = __dirname; | |
// Customize @loopback/boot Booter Conventions here | |
this.bootOptions = { | |
controllers: { | |
// Customize ControllerBooter Conventions here | |
dirs: ['controllers'], | |
extensions: ['.controller.js'], | |
nested: true, | |
}, | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {bind, BindingScope} from '@loopback/core'; | |
import {Credentials} from '../MyAuthenticationStrategy'; | |
import {UserProfile, securityId} from '@loopback/security'; | |
//import {MyUserRepository} from '../repositories'; | |
import {repository, WhereBuilder} from '@loopback/repository'; | |
import {MyUser} from '../models'; | |
//import {USER_PROFILE_NOT_FOUND} from '@loopback/authentication'; | |
@bind({scope: BindingScope.TRANSIENT}) | |
export class MyMachineUserService { | |
constructor( | |
@repository(MyUserRepository) | |
public myUserRepository: MyUserRepository, | |
) {} | |
async verifyCredentials( | |
credentials: Credentials, | |
): Promise<MyUser | undefined> { | |
if ( credentials.username === 'bob' && credentials.password === 'supersecret' ) { | |
return { | |
userId: 42, | |
name: 'bob', | |
foo: 'bar', | |
}; | |
} | |
else { | |
return undefined; | |
} | |
} | |
convertToUserProfile(user: MyUser): UserProfile | undefined { | |
return { | |
[securityId]: '' + user.userId, | |
name: user.name, | |
user: user, | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {inject} from '@loopback/core'; | |
import { | |
AuthenticationStrategy, | |
AuthenticationBindings, | |
AuthenticationMetadata, | |
} from '@loopback/authentication'; | |
import {UserProfile} from '@loopback/security'; | |
import {MyMachineUserService} from './services'; | |
import { | |
MyAuthenticationStrategyBindings, | |
MyAuthenticationOptions, | |
} from './keys'; | |
import {Request} from 'express'; | |
export interface Credentials { | |
username: string; | |
password: string; | |
} | |
export class MyAuthenticationStrategy implements AuthenticationStrategy { | |
name = 'my'; | |
@inject(MyAuthenticationStrategyBindings.DEFAULT_OPTIONS) | |
defaultOptions: MyAuthenticationOptions; | |
constructor( | |
@inject(MyAuthenticationStrategyBindings.MACHINE_USER_SERVICE) | |
private machineUserService: MyMachineUserService, | |
@inject(AuthenticationBindings.METADATA) | |
private authenMetadata: AuthenticationMetadata, | |
) {} | |
async authenticate(request: Request): Promise<UserProfile | undefined> { | |
const options = this.computeAuthenticationOptions(); | |
const credentials = this.extractCredentials(request); | |
if (credentials === undefined) { | |
return undefined; | |
} | |
const user = await this.machineUserService.verifyCredentials(credentials); | |
if (user === undefined) { | |
return undefined; | |
} | |
const userProfile = this.machineUserService.convertToUserProfile(user); | |
return userProfile; | |
} | |
extractCredentials(request: Request): Credentials | undefined { | |
return { | |
username: 'bob', | |
password: '42', | |
}; | |
} | |
computeAuthenticationOptions(): MyAuthenticationOptions { | |
/** | |
Obtain the options object specified in the @authenticate decorator | |
of a controller method associated with the current request. | |
The AuthenticationMetadata interface contains : strategy:string, options?:object | |
We want the options property. | |
*/ | |
return Object.assign( | |
{foo: false}, // the ultimate default, if global default is empty | |
this.defaultOptions || {}, | |
this.authenMetadata.options, | |
) as MyAuthenticationOptions; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {inject} from '@loopback/context'; | |
import { | |
FindRoute, | |
InvokeMethod, | |
ParseParams, | |
Reject, | |
RequestContext, | |
RestBindings, | |
Send, | |
SequenceHandler, | |
} from '@loopback/rest'; | |
import { | |
AuthenticationBindings, | |
AuthenticateFn, | |
AUTHENTICATION_STRATEGY_NOT_FOUND, | |
USER_PROFILE_NOT_FOUND, | |
} from '@loopback/authentication'; | |
const SequenceActions = RestBindings.SequenceActions; | |
export class MySequence implements SequenceHandler { | |
constructor( | |
@inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, | |
@inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, | |
@inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, | |
@inject(SequenceActions.SEND) public send: Send, | |
@inject(SequenceActions.REJECT) public reject: Reject, | |
@inject(AuthenticationBindings.AUTH_ACTION) | |
protected authenticateRequest: AuthenticateFn, | |
) {} | |
// see: https://loopback.io/doc/en/lb4/Loopback-component-authentication.html#adding-an-authentication-action-to-a-custom-sequence | |
async handle(context: RequestContext) { | |
try { | |
const {request, response} = context; | |
const route = this.findRoute(request); | |
//call authentication action | |
console.log(`request path = ${request.path}`); | |
await this.authenticateRequest(request); | |
// Authentication step done, proceed to invoke controller | |
const args = await this.parseParams(request, route); | |
const result = await this.invoke(route, args); | |
this.send(response, result); | |
} catch (error) { | |
if ( | |
error.code === AUTHENTICATION_STRATEGY_NOT_FOUND || | |
error.code === USER_PROFILE_NOT_FOUND | |
) { | |
Object.assign(error, {statusCode: 401 /* Unauthorized */}); | |
} | |
this.reject(context, error); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Uncomment these imports to begin using these cool features! | |
// import {inject} from '@loopback/context'; | |
import {get} from '@loopback/rest'; | |
import {authenticate} from '@loopback/authentication'; | |
import {inject} from '@loopback/core'; | |
import {UserProfile, SecurityBindings} from '@loopback/security'; | |
import {authorize} from '@loopback/authorization'; | |
export class YoController { | |
constructor() {} | |
//@authenticate('my') | |
@authorize({allowedRoles: ['foo', 'bar'], scopes: ['asdf', 'qwer']}) | |
@get('/yo') | |
yo(@inject(SecurityBindings.USER) user: UserProfile): string { | |
return `yo, ${user.name}!`; | |
} | |
//@authenticate('my') | |
@authorize({skip: true}) | |
@get('/sup') | |
sup(): string { | |
return `sup, dude.`; | |
} | |
@get('/nope') | |
nope(): string { | |
return `sorry dude.`; | |
} | |
//@authenticate('my') | |
@get('/yay') | |
yay( | |
@inject(SecurityBindings.USER, {optional: true}) user: UserProfile, | |
): string { | |
if (user) { | |
return `yay ${user.name}!`; | |
} | |
return `yay!`; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment