npm package: @samespace/token-service-koa
Description: Implements Samespace Token Service with koa Middleware
Author: Niket Malik
Latest version: 1.0.9

<img src="https://avatars1.githubusercontent.com/u/50139912?v=4&s=96" alt="Samespace Inc. logo" title="Samespace" align="right" height="96" width="96"/>

# 🔌Token Service (Koa Middleware)

[![npm version](https://img.shields.io/npm/v/@samespace/token-service-koa.svg)](https://www.npmjs.com/package/@samespace/token-service-koa)
[![code cov](https://codecov.io/gh/samespace-inc/token-service-koa/branch/master/graph/badge.svg)](https://codecov.io/gh/samespace-inc/token-service-koa)
[![CircleCI](https://circleci.com/gh/samespace-inc/token-service-koa.svg?style=shield)](https://circleci.com/gh/samespace-inc/token-service-koa)
[![Dependencies](https://david-dm.org/samespace-inc/token-service-koa/status.svg)](https://david-dm.org/samespace-inc/token-service-koa)
[![Known Vulnerabilities](https://snyk.io/test/github/samespace-inc/token-service-koa/badge.svg)](https://snyk.io/test/github/samespace-inc/token-service-koa)

## Table of Contents

- [Getting Started](#getting-started)
- [Usage](#usage)
- [Arguments](#arguments)
- [Helper Functions](#helper-functions)

## Getting started

Install `@samespace/token-service-koa` using npm.

```sh
# NPM
npm install @samespace/token-service-koa --save

# YARN
yarn add @samespace/token-service-koa
```

#### Obtaining Service Account
Service accounts can be obtained by raising a request at [Selfcare](https://cloud.novanet.net)

#### Documentation
Token Service API documentation can be found [here](https://accounts.samespace.com/api/docs).

## Usage
**TokenService** middleware should be the *first middleware* after any `bod-parser`

```js
const Koa = require('koa');
const app = new Koa();

const TokenService = require('@samespace/token-service-koa');
const TokenServiceMiddleware = new TokenService({
  path: '/TokenService',
  audience: 'https://exmaple.com/TokenService',
  keyFile: './serviceAccount.json'
});

app.use(TokenServiceMiddleware);

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
```

## Arguments
 

| Argument  | Type | Description | Required |
| ------------- | ------------- | ------------- | ------------- |
| path | String | Path at which the middleware will listen for Token Server requests.  | ✔️ |
| audience | String | Audience against which *tokens* will be verified.  | ✔️ |
| keyFile | String \| Object | Path from root of service account file or Object containing service account configuration.  | ✔️ |
| ignorePaths | Array | Array of paths or regular expression to ignore *Authentication*<br />E.g: ```ignorePaths: ['/login', /\/(about-us\|contact)/]```<br />This will skip authentication on path `/login` and any path containing the keywords `about-us` or `contact` | ❌ |
| tokenTtl | Integer | TTL of the token to be validated locally, after expiry a `verifyToken` call will be made.  | ❌ |
| onRedirect | Function | Function to trigger when token is not provided by the client and redirection to `redirect_uri` needs to be overridden. | ❌ |
| onExpiry | Function | Function to trigger when an expired token is provided by the client. | ❌ |
| onDeauthorize | Function | Function to trigger when a token is forcefully deauthorized by an audience scope. | ❌ |
| onError | Function | Function to trigger when an unhandled error has occured. | ❌ |

### Reference
#### `onRedirect`
**Default**:
```js
// Deafult will redirect user to redirect_uri
```
**Usage**:
```js
new TokenService({
  onRedirect: (ctx) => {
    ctx.redirect('/login')
  }
})
```

#### `onExpiry`
**Default**:
```js
new TokenService({
  onExpiry: (err, ctx) => {
    ctx.status = 401
    ctx.body = {
      code: 401,
      msg: 'TOKEN_EXPIRY',
      service: 'TokenService'
    }
  }
})
```
**Usage**:
```js
new TokenService({
  onExpiry: (err, ctx) => {
    // Log err to console
    ctx.logger.error(err)
        
    // Set status for client
    ctx.status = 401
  }
})
```

#### `onDeauthorize`
**Default**:
```js
new TokenService({
  onDeauthorize: () => {}
})
```
**Usage**:
```js
new TokenService({
  onDeauthorize: (ctx, deauthorizeToken) => {
    // Log err to console
    ctx.logger.info(`Deauthorized token: ${deauthorizeToken}`)
  }
})
```

#### `onError`
**Default**:
```js
new TokenService({
  onError: (err, ctx) => {
    console.error(err)
    ctx.status = 500
    ctx.body = {
      code: 500,
      msg: 'INTERNAL_SERVICE_ERROR',
      service: 'TokenService'
    }
  }
})
```
**Usage**:
```js
new TokenService({
  onError: (err, ctx) => {
    // Log err to console
    ctx.logger.error(err)
        
    // Set status for client
    ctx.status = 500
  }
})
```

## Helper Functions
These functions can be used to provide additional functionalities.
<br />For ease, they are binded to `ctx.tokenService`.

### `ctx.tokenService.generateToken(String, String)`
```js
/*
 * @async
 * @param {string} subject - Subject for the JWT Token
 * @param {string} claims - JWT Token private claim
 * @return {Promise<string>}
**/
ctx.tokenService.generateToken(subject, claims).then(({ subject, token }) => {
  // ...do something with token
}).catch(err => {
  // ...error handling
})
```
**Rejections:**
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_SUBJECT'
}
```
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_PAYLOAD'
}
```
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_ACCOUNT_CREDENTIALS'
}
```
```js
{
  name: 'TokenServiceError',
  message: 'SERVER_ERROR'
}
```

### `ctx.tokenService.verifyToken(String)`
```js
/*
 * @async
 * @param {string} token - JWT Token to verify
 * @return {Promise<string>}
**/
ctx.tokenService.verifyToken(token).then(() => {
  // Token Verified
}).catch(err => {
  // ...error handling
})
```
**Rejections:**
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_KEY_ID'
}
```
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_TOKEN'
}
```
```js
// Thrown when the token is malformed and the client is 
// unable to verify with the provider.
{
  name: 'JsonWebTokenError',
  message: 'jwt malformed'
}
```


### `ctx.tokenService.refreshToken(String)`
```js
/*
 * @async
 * @param {string} token - JWT Token to refresh
 * @return {Promise<string>}
**/
ctx.tokenService.refreshToken(token).then(freshSignedToken => {
  // provide { freshSignedToken } to the client
}).catch(err => {
  // ...error handling
})
```

### `ctx.tokenService.revokeToken(String)`
```js
/*
 * @async
 * @param {string} subject - subject of JWT Token to deauthorize
 * @return {Promise<string>}
**/
ctx.tokenService.revokeToken(keyId).then(() => {
  // successfully deauthorized the token 
  // across all the clients in audience
}).catch(err => {
  // ...error handling
})
```
**Rejections:**
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_KEY_ID'
}
```

### `ctx.tokenService.deauthorizeToken(String)`
```js
/*
 * @async
 * @param {string} subject - subject of JWT Token to deauthorize
 * @return {Promise<string>}
**/
ctx.tokenService.deauthorizeToken(subject).then(() => {
  // successfully deauthorized the token 
  // across all the clients in audience
}).catch(err => {
  // ...error handling
})
```
**Rejections:**
```js
{
  name: 'TokenServiceError',
  message: 'INVALID_SUBJECT'
}
```

## License

Released under the [MIT license](https://github.com/samespace-inc/token-service-koa/blob/master/LICENSE).