@loopback/authorization
A LoopBack 4 component for authorization support.
Overview
Authorization decides if a subject can perform specific action on an
object.
Role based
Permission based
Vote based
Key building blocks
- Decorate a method to describe:
-
Permission (maps the method to an action on the protected resource)
- Type of the protected resource (such as
customer
or order
) - What action does the method represent (such as
changeEmail
, createOrder
,
or cancelOrder
)
-
ACL (provides role based rules)
-
Voters (supplies a list of function to vote on the decision)
- Intercept a method invocation
Decision matrix
The final decision is controlled by voting results from authorizers and options
for the authorization component.
The following table illustrates the decision matrix with 3 voters and
corresponding options.
Vote #1 | Vote # 2 | Vote #3 | Options | Final Decision |
---|
Deny | Deny | Deny | any | Deny |
Allow | Allow | Allow | any | Allow |
Abstain | Allow | Abstain | any | Allow |
Abstain | Deny | Abstain | any | Deny |
Deny | Allow | Abstain | {precedence: Deny} | Deny |
Deny | Allow | Abstain | {precedence: Allow} | Allow |
Allow | Abstain | Deny | {precedence: Deny} | Deny |
Allow | Abstain | Deny | {precedence: Allow} | Allow |
Abstain | Abstain | Abstain | {defaultDecision: Deny} | Deny |
Abstain | Abstain | Abstain | {defaultDecision: Allow} | Allow |
The options
is described as follows:
export interface AuthorizationOptions {
defaultDecision?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
precedence?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
}
The authorization component can be configured with options:
const options: AuthorizationOptions = {
precedence: AuthorizationDecisions.DENY;
defaultDecision: AuthorizationDecisions.DENY;
}
const binding = app.component(AuthorizationComponent);
app.configure(binding.key).to(options);
Installation
npm install --save @loopback/authorization
Basic use
Start by decorating your controller methods with @authorize
to require the
request to be authorized.
In this example, we make the user profile available via dependency injection
using a key available from @loopback/authorization
package.
import {inject} from '@loopback/context';
import {authorize} from '@loopback/authorization';
import {get} from '@loopback/rest';
export class MyController {
@authorize({allow: ['ADMIN']})
@get('/number-of-views')
numOfViews(): number {
return 100;
}
}
@loopback/authentication
and @loopback/authorization
shares the client
information from the request. Therefore we need another module with
types/interfaces that describe the client, like principles
, userProfile
,
etc... A draft PR is created for this module: see branch
https://github.com/strongloop/loopback-next/tree/security/packages/security
Since the common module is still in progress, as the first release of
@loopback/authorization
, we have two choices to inject a user in the
interceptor:
-
@loopback/authorization
requires @loopback/authentication
as a dependency.
The interceptor injects the current user using
AuthenticationBindings.CURRENT_USER
. Then we remove this dependency in the
common layer PR, two auth modules will depend on @loopback/security
.
- This is what's been done in my refactor PR,
Principle
and UserProfile
are still decoupled, I added a convertor function to turn a user profile
into a principle.
-
The interceptor injects the user using another key not related to
@loopback/authentication
.(Which means the code that injects the user stays
as it is in https://github.com/strongloop/loopback-next/pull/1205). Then we
unify the user set and injection in the common layer PR: same as the 1st
choice, two auth modules will depend on @loopback/security
.
Related resources
Contributions
Tests
run npm test
from the root folder.
Contributors
See
all contributors.
License
MIT