Skip to content

Commit

Permalink
fixup!: apply feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jannyHou committed Mar 21, 2019
1 parent 865e33f commit 38841ba
Show file tree
Hide file tree
Showing 14 changed files with 1,596 additions and 1,635 deletions.
2,666 changes: 1,333 additions & 1,333 deletions package-lock.json

Large diffs are not rendered by default.

116 changes: 75 additions & 41 deletions packages/authentication/authentication-system.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
## Multiple Authentication strategies

An authentication system in a LoopBack 4 application could potentially support multiple popular strategies, including basic auth, oauth2, saml, openid-connect, etc...And also allow programmers to use either a token based or a session based approach to track the logged-in user.
An authentication system in a LoopBack 4 application could potentially support
multiple popular strategies, including basic auth, oauth2, saml, openid-connect,
etc...And also allow programmers to use either a token based or a session based
approach to track the logged-in user.

[comment]: # (Can people do a combination of token based and session based auth?)

The diagram below illustrates the high level abstraction of such an extensible authentication system.
The diagram below illustrates the high level abstraction of such an extensible
authentication system.

<img src="./docs/imgs/multiple-auth-strategies-login.png" width="1000px" />

Assume the app has a static login page with a list of available choices for users to login:
Assume the app has a static login page with a list of available choices for
users to login:

- local: basic auth with email/username + password
- facebook account: oauth2
Expand All @@ -19,86 +22,115 @@ Assume the app has a static login page with a list of available choices for user

For the local login, we retrieve the user from a local database.

For the third-party service login, e.g. facebook account login, we retrieve the user info from facebook authorization server using oauth2, then find or create the user in the local database.
For the third-party service login, e.g. facebook account login, we retrieve the
user info from the facebook authorization server using oauth2, then find or
create the user in the local database.

By clicking any one of the links, you login with a particular account and your status will be tracked in a session(with session-based auth), or your profile will be encoded into a JWT token(with token-based auth).
By clicking any one of the links, you login with a particular account and your
status will be tracked in a session(with session-based auth), or your profile
will be encoded into a JWT token(with token-based auth).

A common login flow for all strategies would be: the authentication action verifies the credentials and returns the raw information of that logged-in user.
A common flow for all the login strategies would be: the authentication action
verifies the credentials and returns the raw information of that logged-in user.

Here the raw information refers to the data returned from a third-party service or a persistent database. Therefore you need another step to convert it to a user profile instance which describes your application's user model. Finally the user profile is either tracked by a generated token or a session + cookie.
Here the raw information refers to the data returned from a third-party service
or a persistent database. Therefore you need another step to convert it to a
user profile instance which describes your application's user model. Finally the
user profile is either tracked by a generated token OR a session + cookie.

The next diagram illustrates the flow of verifying the client requests sent after the user has logged in.
The next diagram illustrates the flow of verifying the client requests sent
after the user has logged in.

<img src="./docs/imgs/multiple-auth-strategies-verify.png" width="1000px" />

The request goes through the authentication action which decodes/deserializes the user profile from token/session, binds it to the request context so that actions after 'authenticate' could inject it using DI.
The request goes through the authentication action which invokes the
authentication strategy to decode/deserialize the user profile from the
token/session, binds it to the request context so that actions after
'authenticate' could inject it using DI.

Next let's walk through the typical API flow of user login and user verification.
Next let's walk through the typical API flow of user login and user
verification.

## API Flows (using JWT as example)
## API Flows (using BasicAuth + JWT as example)

Other than the LoopBack core and its authentication module, there are different services included and integrated together to perform the authentication.
Other than the LoopBack core and its authentication module, there are different
parts included and integrated together to perform the authentication.

The next diagram, using the JWT authentication strategy as an example, draws two API flows:
The next diagram, using the BasicAuth + JWT authentication strategy as an
example, draws two API flows:

- Login: user login with email+password
- Verify: verify the logged-in user

along with the responsibilities divided among different services:
along with the responsibilities divided among different parts:

- LoopBack core: resolve a strategy based on the endpoint's corresponding
authentication metadata, execute the authentication action which invokes the
strategy's `authenticate` method.

- LoopBack core: resolve a strategy based on the endpoint's corresponding authentication metadata, invoke the authentication action provided as strategy functions.
- Authentication strategy:

- Authentication strategy provider:
- (login flow) verify user credentials and return a token that tracks the user.
- (login flow) verify user credentials and return a user profile(it's up to
the programmer to create the JWT access token inside the controller
function).
- (verify flow) verify the token and decode user profile from it.

- Authentication services: some utility services that can be injected in the strategy class. (Each service's functionalities will be covered in the next section)
- Authentication services: some utility services that can be injected in the
strategy class. (Each service's functionalities will be covered in the next
section)

_Note: FixIt! the step 6 in the following diagram should be moved to LoopBack
side_

<img src="./docs/imgs/API-flow-(JWT).png" width="1000px" />

_Note: Another section for session based auth TBD_

## Authentication framework architecture

The following diagram describes the architecture of the entire authentication framework and the detailed responsibility of each part.
The following diagram describes the architecture of the entire authentication
framework and the detailed responsibility of each part.

You can check the pseudo code in folder `docs` for:

- [authentication-action](./docs/authentication-action.md)
- [authentication-strategy](./docs/authentication-strategy.md)
- [endpoints defined in controller](./docs/controller-functions.md)
- [basic auth strategy](./docs/basic-auth.md)
- [jwt strategy](./docs/strategies/jwt.md)
- [oauth2 strategy](./docs/strategies/oauth2.md)

And the abstractions for:

- [login service](./src/services/login.service.ts)
- [transport/client service](./src/services/client.service.ts)
- [user service](./src/services/user.service.ts)
- [token service](./src/services/token.service.ts)

<img src="./docs/imgs/auth-framework-architecture.png" width="1000px" />


### Token based authentication

- Login flow

- authentication action:
- resolve metadata to get the strategy
- invoke strategy.login()
- invoke strategy.authenticate()
- set the current user as the return of strategy.authenticate()
- strategy:
- extract credentials from
- transport layer (call credential transport service)
- or local configuration file (call credential transport service)
- verify credentials (call login service) and get the user profile
- generate token (call token service)
- return token
- extract credentials from
- transport layer(request)
- or local configuration file
- verify credentials and return the user profile (call user service)
- controller function:
- TBD: How to serialize the token into response? Do we want to do it in controller?
- generate token (call token service)
- return token or serialize it into the response

- Verify flow
- authentication action:
- resolve metadata to get the strategy
- invoke strategy.verify()
- set the current user as the return of strategy.verify()
- invoke strategy.authenticate()
- set the current user as the return of strategy.authenticate()
- strategy:
- extract access token from transport layer(call token transport service)
- extract access token from transport layer(request)
- verify access token(call token service)
- decode user from access token(call token service)
- return user
Expand All @@ -108,21 +140,23 @@ And the abstractions for:
### Session based authentication

- Login flow

- authentication action:
- resolve metadata to get the strategy
- invoke strategy.login()
- invoke strategy.authenticate()
- strategy:
- extract credentials from
- extract credentials from
- transport layer (call credential transport service)
- or local configuration file (call credential transport service)
- verify credentials (call login service) and get the user profile
- serialize user info into session(call session service)
- verify credentials (call user service) and return the user profile
- controller:
- serialize user info into the session

- Verify flow
- authentication action:
- resolve metadata to get the strategy
- invoke strategy.verify()
- set the current user as the return of strategy.verify()
- invoke strategy.authenticate()
- set the current user as the return of strategy.authenticate()
- strategy:
- extract session info from cookie(call session service)
- deserialize user info from session(call session service)
Expand Down
36 changes: 13 additions & 23 deletions packages/authentication/docs/authentication-action.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
### Auth action

```ts
import * as HttpErrors from 'http-errors';

async action(request: Request): Promise<UserProfile | undefined> {
const strategy = await this.getStrategy();
if (!strategy) {
const authStrategy = await this.getAuthStrategy();
if (!authStrategy) {
// The invoked operation does not require authentication.
return undefined;
}

// read the action metadata from endpoint's auth metadata like
// `@authenticate('strategy_name', {action: 'login'})`
// type ActionType = 'login' | 'verify'
const action = await this.getAction();
if (!action) {
throw new Error('no action specified for your endpoint')
}
if (!strategy[action]) {
throw new Error('invalid strategy parameter');
}

let user: UserProfile;
try {
switch(action) {
case 'login': user = strategy.login(request);
case 'verify': user = strategy.verify(request);
default: return;
const userProfile: UserProfile = await authStrategy.authenticate(request);
this.setCurrentUser(userProfile);
// a convenient return for the next request handlers
return userProfile;
} catch (err) {
// interpret the raw error code/msg here and throw the corresponding HTTP error
// convert it to http error
if (err.code == '401') {
throw new HttpErrors.Unauthorized(err.message);
}
} catch(err) {
if (err) throw err;
}

this.setCurrentUser(user);
return user;
}
```
46 changes: 12 additions & 34 deletions packages/authentication/docs/authentication-strategy.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
### Auth strategy class

There are two flavors of defining the strategy interface. One is having only one function `authenticate` which takes in the action type and handles login and verify accordingly. The other one is having two functions `login` and `verify`.

After having the sync up meeting with Raymond we agreed on flavor #1. While when we were writing the pseudo code for strategies + services, we felt the word `authenticate` is very misleading and switched back to having two separate functions to distinguish between the login and verify flow.

A summary of the pros/cons of each flavor will be provided later. Discussion/feedback are welcomed. Currently this PR uses flavor #2.

- flavor 1

```ts

type ActionType = 'login' | 'verify';

class AuthticationStrategy {
authenticate(option: {action: ActionType}): Promise<UserProfile | undefined> {
// 1. Try to find current user
// 2. If found, return it
// 3. If not found:
// 3.1 if /login invokes this action,
// performs login using this particular strategy
// 3.2 if other API invokes this action, then throw 401 error
};
}
```

- flavor 2

```ts
class AuthticationStrategy {
login(request, response): Promise<UserProfile | undefined> {};

verify(request): Promise<UserProfile | undefined> {
// 1. Try to find current user
// 2. If found, return it
// 3. If not found, throw 401:
}
import {Request} from '@loopback/rest';

interface AuthenticationStrategy {
// The resolver will read the options object from metadata, call `strategy.setOptions`
options: object;
authenticate(request: Request): Promise<UserProfile | undefined>;
setOptions(options: object);
// This is a private function that extracts credential fields from a request,
// it is called in function `authenticate`. You could organize the extraction
// logic in this function or write them in `authenticate` directly without defining
// this extra utility.
private extractCredentials?(request: Request): Promise<Credentials>;
}
```
Loading

0 comments on commit 38841ba

Please sign in to comment.