diff --git a/packages/authorization/README.md b/packages/authorization/README.md index fc6d18c69917..99b5a5bfdff5 100644 --- a/packages/authorization/README.md +++ b/packages/authorization/README.md @@ -118,6 +118,27 @@ export class MyController { } ``` +Please note that `@authorize` can also be applied at class level for all methods +within the class. In the code below, `numOfViews` is protected with +`BasicStrategy` (inherited from the class level) while `hello` does not require +authorization (skipped by `@authorize.skip`). + +```ts +@authorize({allow: ['ADMIN']}) +export class MyController { + @get('/number-of-views') + numOfViews(): number { + return 100; + } + + @authorize.skip() + @get('/hello') + hello(): string { + return 'Hello'; + } +} +``` + ## Extract common layer(TBD) `@loopback/authentication` and `@loopback/authorization` shares the client diff --git a/packages/authorization/src/__tests__/unit/authorize-decorator.test.ts b/packages/authorization/src/__tests__/unit/authorize-decorator.test.ts index 72386abf3434..6f6d88c8cdfe 100644 --- a/packages/authorization/src/__tests__/unit/authorize-decorator.test.ts +++ b/packages/authorization/src/__tests__/unit/authorize-decorator.test.ts @@ -155,6 +155,16 @@ describe('Authentication', () => { }); }); + it('can skip authorization with a flag', () => { + class TestClass { + @authorize.skip() + getSecret() {} + } + + const metaData = getAuthorizationMetadata(TestClass, 'getSecret'); + expect(metaData).to.eql({skip: true}); + }); + it('can stack decorators to target method', () => { class TestClass { @authorize.allow('a1', 'a2') diff --git a/packages/authorization/src/authorize-interceptor.ts b/packages/authorization/src/authorize-interceptor.ts index e144f9384d5a..ef2f5cf05d1c 100644 --- a/packages/authorization/src/authorize-interceptor.ts +++ b/packages/authorization/src/authorize-interceptor.ts @@ -68,7 +68,7 @@ export class AuthorizationInterceptor implements Provider { debug('No authorization metadata is found for %s', description); } metadata = metadata || this.options.defaultMetadata; - if (!metadata) { + if (!metadata || (metadata && metadata.skip)) { debug('Authorization is skipped for %s', description); const result = await next(); return result; diff --git a/packages/authorization/src/decorators/authorize.ts b/packages/authorization/src/decorators/authorize.ts index ba3dd971d24d..4c68bbac69b1 100644 --- a/packages/authorization/src/decorators/authorize.ts +++ b/packages/authorization/src/decorators/authorize.ts @@ -88,6 +88,7 @@ export class AuthorizeMethodDecoratorFactory extends MethodDecoratorFactory< return list; } } + /** * Decorator `@authorize` to mark methods that require authorization * @@ -191,6 +192,11 @@ export namespace authorize { * Deny unauthenticated users */ export const denyUnauthenticated = () => deny(UNAUTHENTICATED); + + /** + * Skip authorization + */ + export const skip = () => authorize({skip: true}); } /** diff --git a/packages/authorization/src/types.ts b/packages/authorization/src/types.ts index ae95ec3e30f9..1691a33278a1 100644 --- a/packages/authorization/src/types.ts +++ b/packages/authorization/src/types.ts @@ -58,6 +58,10 @@ export interface AuthorizationMetadata { * Define the access scopes */ scopes?: string[]; + /** + * A flag to skip authorization + */ + skip?: boolean; } /**