npm package: @leisurelink/auth-context
Description: LeisureLink client-side authentication & authorization context for node.
Author: Phillip Clark
Latest version: 3.0.0

# auth-context

Client-side convenience classes for decoding, trusting, and reasoning about `endpoint` and `user` _authority_ within LeisureLink's federated security.

The __auth__ in `auth-context` refers to both _authentication_ and _authorization_ &mdash; for brevity we refer to these as someone's _authority_. The actual security primatives encoded in our `auth-tokens` are _security claims_, these include _facts_, _roles_, and _permissions_. [More precise information about claims is avaiable in the `claims` module's README](https://github.com/LeisureLink/claims).

## Install

```bash
npm install --save @leisurelink/auth-context
```

## Use

### Import It

```javascript
var auth = require('@leisurelink/auth-context');
```

> **All subsequent examples assume this import!**

### Create an `AuthScope`

```javascript
var options = {
  issuer: 'test',                      // JWT issuer we trust
  audience: 'test',                    // JWT audience we expect
  issuerKeyFile: './test/test-key.pub' // The issuer's public key, so we can verify the issuer's digital signature
};

var scope = new auth.AuthScope(options);
```

### Verifying an _auth-token_ Creates an `AuthContext`

```javascript
// assuming we've created a scope...
scope.verify(token, (err, ctx) => {
  if (err) {
    console.log(`Unable to verify the specified auth-token: ${err}`);
  }

  assert.equal(ctx.verified, true);
  assert.equal(ctx.isExpired, false);

  console.log(`token is valid for ${ctx.principalId}(${ctx.email}) until ${ctx.expiresAt}`);
});
```

### Inspect a Principal's Claims (fact example)

```javascript
// assume app.user is-an AuthContext, such as when using trusted-app...

function userInfoMiddleware(req, res, next) {
  let app = req.app;
  // sys claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/sys-claimset-spec.json
  // usr claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/usr-claimset-spec.json
  app.user.get('#/sys/em', '#/usr/fn', '#/usr/ln', (err, res) => {
    if (err) {
      next(err);
    }

    res.locals.email = res['#/sys/em'];
    res.locals.firstName = res['#/usr/fn'];
    res.locals.lastName = res['#/usr/ln'];
    next();
  });
}
```

### Check for Principal Role Membership (role example)

```javascript
// assume app.remoteAuth is-an AuthContext, such as when using trusted-app...

function remoteEndpointPrincipalIsSecurityOfficer(req, res, next) {
  let app = req.app;
  // sys claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/sys-claimset-spec.json
  app.remoteAuth.has('#/sys/secofr', (err, res) => {
    if (err) {
      next(err);
    }

    res.locals.endpointIsSecurityOfficer = res; // roles are truth values.
    next();
  });
}
```

### Check if Principal Has Permission (permission example)

```javascript
// assume app.user is-an AuthContext, such as when using trusted-app...

function userPrincipalReadUpdatePermissions(req, res, next) {
  let app = req.app;
  // sys claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/sys-claimset-spec.json
  // NOTE: we're only querying for read(r) and update(u) permissions. Permissions are a set of flags, refer to the sys claimset spec for the defined permission flags [acrudq].
  app.user.get('#/sys/pri[ru]', (err, res) => {
    if (err) {
      next(err);
    }

    // response (truth) is always in relation to the permissions queried.
    res.locals.canReadPrincipals = res;
    res.locals.canUpdatePrincipals = res;
    next();
  });
}
```

### Identity Claims

A subset of a principal's claims are designated **_identity claims_**; these claims are encoded in the principal's `auth-token` and provide a short-curcuit trust. The above examples may invoke a remote call to the _claims authority_, thus they are asynchronous. In the case of _identity claims_, a remote call is not necessary and there are synchronous alternatives provided through the `AuthContext.ident` property:

### Inspect a Principal's Identity Claims (fact)

```javascript
// assume app.user is-an AuthContext, such as when using trusted-app...

function userInfoMiddleware(req, res, next) {
  let user = req.app.user;
  // sys claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/sys-claimset-spec.json
  // usr claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/usr-claimset-spec.json
  res.locals.email = user.ident.get('#/sys/em');
  res.locals.firstName = user.ident.get('#/usr/fn');
  res.locals.lastName = user.ident.get('#/usr/ln');
  next();
}
```

### Check for Principal's Identity Role Membership

```javascript
// assume app.remoteAuth is-an AuthContext, such as when using trusted-app...

function remoteEndpointPrincipalIsSecurityOfficer(req, res, next) {
  let remoteAuth = req.app.remoteAuth;
  // sys claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/sys-claimset-spec.json

  res.locals.endpointIsSecurityOfficer = remoteAuth.ident.has('#/sys/secofr');
  next();
}
```

### Check Principal's Identity Permissions

```javascript
// assume app.user is-an AuthContext, such as when using trusted-app...

function userPrincipalReadUpdatePermissions(req, res, next) {
  let user = req.app.user;
  // sys claimset spec: https://github.com/LeisureLink/authentic-api/blob/develop/data/sys-claimset-spec.json
  // NOTE: we're only querying for read(r) and update(u) permissions. Permissions are a set of flags, refer to the sys claimset spec for the defined permission flags [acrudq].

  res.locals.canReadPrincipals = user.ident.get('#/sys/pri[r]');
  res.locals.canUpdatePrincipals = user.ident.get('#/sys/pri[u]');
  next();
}
```

## API

`@leisurelink/auth-context` defines the following classes:

* [`AuthScope`](#user-content-authscope-class) &ndash; An encapsulation of information identifying an _authority_ that we trust to sign `auth-tokens`.
* [`AuthContext`](#user-content-authcontext-class) &ndash; An encapsulation of a decoded `auth-token` and provides convenience methods for accessing the _authenticated principal_ associated with the `auth-token`, as well as that _principal's_ authorization information.

### `AuthScope` Class

The `AuthScope` class provides a mechanism by which we partition the security space. Notably, each _scope_ encapsulates trust for a particular _issuer_ and _audience_ \[these terms are defined by [JWT](http://jwt.io/), which we use internally to generate `auth-tokens`\].

#### .constructor(options)

A constructor that creates a new `AuthScope` instance using the specified options.

_arguments:_
* `options` : _object, optional_ &ndash; an object specifying:
  * `issuer` : _string, optional_ &ndash; the name of the _issuer_ that we trust to issue and digitally sign _auth-tokens_. Default: 'test'.
  * `audience` : _string, optional_ &ndash; the name of the _audience_, among the audiences that the _issuer_ issues _auth-tokens_ for, that the new scope should trust. Default: 'test'.
  * `issuerKeyFile` : _string, optional_ &ndash; the file system path to the _issuer's_ public key, used to verify the `auth-token`'s signature.
  * `issuerKey` : _Buffer, optional_ &ndash; the _issuer's_ public key, used to verify the `auth-token`'s signature.

Either `options.issuerKeyFile` or `options.issuerKey` is required in order to verify _auth-tokens_.

_example:_
```javascript
var options = {
  issuer: 'test',                      // JWT issuer we trust
  audience: 'test',                    // JWT audience we expect
  issuerKeyFile: './test/test-key.pub' // The issuer's public key, so we can verify the issuer's digital signature
};

var scope = new auth.AuthScope(options);
```

#### .issuer

A _readonly_ property that indicates the name of the _issuer_ trusted by the scope.

#### .audience

A _readonly_ property that indicates the _audience_ in which the scope is participating.

#### .verify(token, callback)

A method that verifies the specified `auth-token`.

_arguments:_
* `token` : _string, required_ &ndash; the `auth-token` to be verified.
* `callback` : _function, required_ &ndash; a function with signature `callback(err, context)`; called with the results of the operation:
  * `err` : _Error_ &ndash; specified upon error; indicates what went wrong
  * `context` : _AuthContext_ &ndash; specified upon success, must be inspected to determine the token's validity.

_example:_

```javascript
scope.verify(token, (err, ctx) => {
  if (err) {
    console.log(`Oopsie; an unexpected: ${err}`);
  }
  if (ctx.verified) {
    // ok, indeed signed by the issuer we trust...
    if (!ctx.isExpired) {
      // ok, its still valid...
      console.log(`Verified an auth-token for: ${ctx.principalId}`);
    }
  }
});
```


### `AuthContext` Class

The `AuthContext` class encapsulates a decoded `auth-token` and provides properties and methods over the _token_ enabling inspection, interrogation, and reasoning about the _authenticated principal_ and their _security claims_.

It is important to note that not all _claims_ are encoded in the `auth-token`. In a large service inventory the total number of _security claims_ proffered to the _claims authority_ on behalf of a _security principal_ could be numerous, making it unfeasable to encode all such claims in the `auth-token`. Therefore, a subset of _security claims_, designated __identity claims__, are encoded in and travel with the `auth-token`. All other claims are resolved on demand and cached inside the `AuthContext`.

It is anticipated that `AuthContext` has a short lifespan. In most cases they should last no-longer than a single web request or API operation. This scheme enables efficient propagation of _security claims_ across the network without undue likelihood of encountering stale authorizations. _Perform your own testing to ensure you reach an appropriate level of assurance as to the veracity of a principal's security claims._

`AuthContext` exposes _readonly_ properties for many of the most frequently used _claims_ contained in an `auth-token`:
* `id` &ndash; indicates the `auth-token`'s unique identity; corresponds with JWT's `jti` property.
* `systemId` &ndash; a (_relatively short_), system assigned, globally unique identifier for the principal.
* `principalId` &ndash; indicates the principal's _human readable identity_. In most cases, for principals of kind __usr__, `principalId` will be the user's primary email address. For principals of kind __end__, `principalId` will be the endpoint/system's well-known, unique name.
* `kind` &ndash; indicates the principal's _kind_; kinds are __usr__ for human users and __end__ for trusted endpoints/systems.
* `lang` &ndash; inidcates the [BCP-47 language code](https://tools.ietf.org/html/bcp47) of the principal's preferred language.
* `firstName` &ndash; for principals of kind __usr__, the user's first name.
* `lastName` &ndash; for principals of kind __usr__, the user's last name.
* `email` &ndash; the principal's primary email address; for principals of kind __end__, `email` refers to the primary contact's email address.
* `expiresAt` &ndash; the `auth-ticket`'s expiration date.

__Understanding the Identifiers__

The fact that an `auth-token` has 3 identifiers can be a point of confusion. It is important to understand the purpose and _intended use_ of each identifier:

| identifier | purpose | intended use|
| --- | --- | --- |
| `systemId` | Immutable, globally unique, system defined identifier. | Used throughout the federated system to refer to the principal across authentication sessions, such as when stored on the file system or in a database.|
| `principalId` | The principal's _human readable_ identifier. These __should__ be unique within the federated system but may change over time, such is the case when emails are used for this value or when user-selected _screen names_ are used. | Used throughout the federated system to instill user confidence that we know who they are, such as a label. (_logged in as_: __fakedood@noobtube.co__) |
| `id` | Immutable `auth-token` identifier. | Uniquely identifies a single authenticated session. May be used throughout the federation to refer to a principal's session. |

#### .constructor(ticket, verified, token, resolverConfig)

A constructor that creates a new `AuthContext` instance.

_arguments:_
* `ticket` : _object, optional_ &ndash; an object representation of the decoded `auth-token`.
* `verified` : _boolean, optional_ &ndash; indicates whether the `auth-token`'s digital signature verified.
* `token` : _string, optional_ &ndash; the original `auth-token`.
* `resolverConfig` : _object, optional_ &ndash; a trusted configuration object used to resolve _security claims_ not present in the `auth-token`.

#### .get(claimId, callback)
This method has variable arity:
* __.get(claimId)__ // deprecated
* __.get([claimId, claimId, ...], callback)__
* __.get(claimId, claimId, ..., callback)__

Gets the principal's claim corresponding to the specified `claimId`(s).

_arguments:_
* `claimId` : _string, required_ &ndash; specifies one or more _claim identifiers_ to resolve. _Claim identifiers_ are encoded as [RFC 6901 JSON Pointers in URI Fragment Identifier Representation](https://tools.ietf.org/html/rfc6901#section-6), see [the `claims` module's README for more about _claim identifiers_](https://github.com/LeisureLink/claims).
* `callback` : _function, optional_ &ndash; a function with signature `callback(err, result)`; called with the results of the operation

_returns:_
* An object is returned upon success. The object's properties will correspond to each `claimId` specified by the caller.

```javascript
// Get first name, last name, and email address from the auth-token...
context.get('#/usr/fn', '#/usr/ln', '#/sys/em', (err, res) => {
    if (err) {
      console.log(`An unexpected error occurred: ${err}`);
      return;
    }

    console.log(`The authenticated user is: ${res['#/usr/fn']} ${res['#/usr/ln']} <${res['#/sys/em']}>`);
    // The authenticated user is: Phillip Clark <pclark@leisurelink.com>
  });
```

> NOTE: One of the reasons a `claimId` is-a JSON Pointer is that this encoding provides us with a namespacing mechanism. In the example above, the _claims_ in our query come from two different namespaces; __sys__ and __usr__. As you work with _claims_, keep in mind that the first JSON Pointer path segment identifies a **_claim set_**. Each _claim_ is a member of a _claim set_; these _claim sets_ may be proffered to federated security by different micro-services performing the role of **_claim set provider_**.

#### .has(claimId, callback)
This method has variable arity:
* __.has(claimId)__ // deprecated
* __.has([claimId, claimId, ...], callback)__
* __.has(claimId, claimId, ..., callback)__

This method is an alias for:
* __.hasAllOf(claimId)__ // deprecated
* __.hasAllOf([claimId, claimId, ...], callback)__
* __.hasAllOf(claimId, claimId, ..., callback)__
* __.role(claimId)__ // deprecated
* __.role([claimId, claimId, ...], callback)__
* __.role(claimId, claimId, ..., callback)__

Determines if the principal has claims corresponding to the specified `claimId`(s).

This method is often used to determine if a principal is a member of a _role_. Remember, there are 3 types of _claims_: _facts_, _roles_, and _permissions_. _Facts_ and _permissions_ have associated values, so should be retrieved and evaluated via `.get(claimId, callback)`, whereas _roles_ are truth values, meaning if the _role_ is present the principal _is a member_.

_arguments:_
* `claimId` : _string, required_ &ndash; specifies one or more _claim identifiers_ to resolve. _Claim identifiers_ are encoded as [RFC 6901 JSON Pointers in URI Fragment Identifier Representation](https://tools.ietf.org/html/rfc6901#section-6), see [the `claims` module's README for more about _claim identifiers_](https://github.com/LeisureLink/claims).
* `callback` : _function, optional_ &ndash; a function with signature `callback(err, result)`; called with the results of the operation

_returns:_
* A boolean (`result`) indicating whether the principal has all of the specified claims. If an error occurs, returns the error as `err`.

_examples:_

```javascript
// Check whether the principal is a member of the sysadmin role...
context.has('#/sys/adm', (err, res) => {
    if (err) {
      console.log(`An unexpected error occurred: ${err}`);
      return;
    }

    if (res) {
      console.log(`Principal is a sysadmin: ${context.principalId}`);
    }
  });
```

```javascript
// Check for an email address, first name, and last name...
context.has('#/sys/em', '#/usr/fn', '#/usr/ln', (err, res) => {
    if (err) {
      console.log(`An unexpected error occurred: ${err}`);
      return;
    }

    if (res) {
      console.log('Looks like a user prinicpal!')
    } else {
      console.log('Probably a system/endpoint principal.');
    }
  });
```

#### .any(claimId, callback)
This method has variable arity:
* __.any(claimId)__ // deprecated
* __.any([claimId, claimId, ...], callback)__
* __.any(claimId, claimId, ..., callback)__

This method is an alias for:
* __.hasAnyOf(claimId)__ // deprecated
* __.hasAnyOf([claimId, claimId, ...], callback)__
* __.hasAnyOf(claimId, claimId, ..., callback)__

Determines if the principal has any of the claims corresponding to the specified `claimId`(s).

_arguments:_
* `claimId` : _string, required_ &ndash; specifies one or more _claim identifiers_ to resolve. _Claim identifiers_ are encoded as [RFC 6901 JSON Pointers in URI Fragment Identifier Representation](https://tools.ietf.org/html/rfc6901#section-6), see [the `claims` module's README for more about _claim identifiers_](https://github.com/LeisureLink/claims).
* `callback` : _function, optional_ &ndash; a function with signature `callback(err, result)`; called with the results of the operation

_returns:_
* A boolean (`result`) indicating whether the principal has any of the specified claims. If an error occurs, returns the error as `err`.

_examples:_

```javascript
// Check for a principalId...
context.any('#/sys/pid', (err, res) => {
    if (err) {
      console.log(`An unexpected error occurred: ${err}`);
      return;
    }

    assert.equal(res, true); // all auth-tokens have a principalId!
  });
```

```javascript
// Check for a first name, last name, or an email address...
context.any('#/usr/fn', '#/usr/ln', '#/sys/em', (err, res) => {
    if (err) {
      console.log(`An unexpected error occurred: ${err}`);
      return;
    }

    if (res) {
      console.log('Yep, we\'ve got some human readable identifying info!');
    }
  });
```

## Dependent Types

These are types that types in this repository may use

* crProvider (Claim Resolution Provider) - A object (or library) which exposes methods for resolving local
and remote claims. A object must minimally expose two methods to be considered a crProvider:

    * crProvider#getSpecResolver(lang, authenticClient) - getSpecResolver must return a function implementing ```function (csid, callback)``` where csid is a Claimset Id
    * crProvider#getClaimResolver(lang, authenticClient) - getClaimResolver must return a function implementing ```function (clid, systemId, callback)``` where clid is a Claim Id and SystemId is an identifier for a principal.

More information about the nature of the requirements placed on the returned functions can be found [here](https://github.com/LeisureLink/claims).

Both of the Claim Resolutoin Provider functions accept a lang, e.g. en_US, and an instatiated [AuthenticClient](https://github.com/LeisureLink/authentic-client) object.