Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,190 changes: 0 additions & 7,190 deletions package-lock.json

This file was deleted.

189 changes: 189 additions & 0 deletions packages/authentication/docs/authentication-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,192 @@ And the abstractions for:
- return user
- controller function:
- process the injected user

## Registering an authentication strategy via an extension point

Authentication strategies register themselves to an authentication strategy
resolver using an
[ExtensionPoint/Extension Pattern](https://wiki.eclipse.org/FAQ_What_are_extensions_and_extension_points%3F)
as described in the
[Greeter extension example](https://github.com/strongloop/loopback-next/tree/master/examples/greeter-extension).

The `AuthenticationStrategyProvider` class in
`src/providers/auth-strategy.provider.ts` declares an `extension point` named
`AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME` via the
`@extensionPoint` decorator. `AuthenticationStrategyProvider` is responsible for
returning an authentication strategy which has a `specific name` and has been
registered as an `extension` of the aforementioned `extension point` with the
aid of the `@extensions()` decorator. The binding scope is set to `transient`
because an authentication strategy `may` differ with each request.

```ts
@extensionPoint(
AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME,
{scope: BindingScope.TRANSIENT},
)
export class AuthenticationStrategyProvider
implements Provider<AuthenticationStrategy | undefined> {
constructor(
@inject(AuthenticationBindings.METADATA)
private metadata: AuthenticationMetadata,
@extensions()
private authenticationStrategies: Getter<AuthenticationStrategy[]>,
) {}
value(): ValueOrPromise<AuthenticationStrategy | undefined> {
if (!this.metadata) {
return;
}
const name = this.metadata.strategy;

return this.findAuthenticationStrategy(name).then(function(strategy) {
if (strategy) {
return strategy;
} else {
// important not to throw a non-protocol-specific error here
throw new AuthenticationStrategyNotFoundError(
`The strategy '${name}' is not available.`,
);
}
});
}

async findAuthenticationStrategy(name: string) {
const strategies = await this.authenticationStrategies();
const matchingAuthStrategy = strategies.find(a => a.name === name);
return matchingAuthStrategy;
}
}
```

The `name` of the strategy is specified in the `authenticate` decorator that is
added to a controller method when authentication is desired for a specific
endpoint.

```ts
class UserController {
constructor() {}
@get('/whoAmI')
@authenticate('basic')
whoAmI()
{
...
}
}
```

An authentication strategy must implement the `AuthenticationStrategy` interface
defined in `src/types.ts`.

```ts
export interface BasicAuthenticationStrategyCredentials {
email: string;
password: string;
}

export class BasicAuthenticationStrategy implements AuthenticationStrategy {
name: string = 'basic';

constructor(
@inject(BasicAuthenticationStrategyBindings.USER_SERVICE)
private user_service: BasicAuthenticationUserService,
) {}

async authenticate(request: Request): Promise<UserProfile | undefined> {
const credentials: BasicAuthenticationStrategyCredentials = this.extractCredentals(
request,
);
const user = await this.user_service.verifyCredentials(credentials);
const userProfile = this.user_service.convertToUserProfile(user);

return userProfile;
}
```

A custom sequence must be created to insert `AuthenticationBindings.AUTH_ACTION`
action. The `AuthenticateFn` function interface is implemented by the `value()`
function of `AuthenticateActionProvider` class in
`src/providers/authentication-action.provider.ts`.

```ts
class SequenceIncludingAuthentication 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) protected send: Send,
@inject(SequenceActions.REJECT) protected reject: Reject,
@inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}

async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
const args = await this.parseParams(request, route);

//
// The authentication action utilizes a strategy resolver to find
// an authentication strategy by name, and then it calls
// strategy.authenticate(request).
//
// The strategy resolver throws a non-http error if it cannot
// resolve the strategy. It is necessary to catch this error
// and rethrow it as in http error (in our REST application example)
//
// Errors thrown by the strategy implementations are http errors
// (in our REST application example). We simply rethrow them.
//
try {
//call authentication action
await this.authenticateRequest(request);
} catch (e) {
// strategy not found error
if (e instanceof AuthenticationStrategyNotFoundError) {
throw new HttpErrors.Unauthorized(e.message);
} //if
else {
// strategy error
throw e;
} //endif
} //catch

// Authentication successful, proceed to invoke controller
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
this.reject(context, error);
return;
}
}
}
```

Then custom sequence must be bound to the application, and the authentication
strategy must be added as an extension of the extension point using the
`addExtension` function.

```ts
export class MyApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options?: ApplicationConfig) {
super(options);

this.component(AuthenticationComponent);

this.sequence(SequenceIncludingAuthentication);

addExtension(
this,
AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME,
BasicAuthenticationStrategy,
{
namespace:
AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME,
},
);
}
}
```
114 changes: 114 additions & 0 deletions packages/authentication/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/authentication/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"@types/node": "^10.11.2",
"@types/passport": "^1.0.0",
"@types/passport-http": "^0.3.6",
"passport-http": "^0.3.0"
"passport-http": "^0.3.0",
"jsonwebtoken": "^8.5.1"
},
"keywords": [
"LoopBack",
Expand Down
Loading