npm package: @sakuraapi/auth-native-authority
Description: Middleware to support native authentication capabilities
Author: Jean-Pierre E. Poveda
Latest version: 0.6.2

# Status
|Branch     |Status     |
|-----------|-----------|
| Develop   |[![Build Status](https://travis-ci.org/sakuraapi/auth-native-authority.svg?branch=develop)](https://travis-ci.org/sakuraapi/auth-native-authority)| 
| Master    |[![Build Status](https://travis-ci.org/sakuraapi/auth-native-authority.svg?branch=master)](https://travis-ci.org/sakuraapi/auth-native-authority)|

# @sakuraapi/auth-native-authority

Middleware that adds native (email/password) authentication authority capabilities to a SakuraApi server. An "authentication authority" (in SakuraApi land) is a micro-service / server that is responsible for authentication on behalf of itself and other micro-services. Other SakuraApi micro-services trusting this authority will implement a separate package for consuming the tokens generated by the authentication authority.

```
npm install @sakuraapi/auth-native-authority
```

# Integration
Somewhere, early, in the bootstrapping of SakuraApi (this example assumes you're following the pattern of having a `sakura-api.ts` file that boostraps SakuraAPI):

```
import {SakuraApi} from '@sakuraapi/api';
import {dbs} from './config/db';
import {addAuthenticationAuthority} from '@sakuraapi/auth-native-authority';
import bodyParser = require('body-parser');
import helmet = require('helmet');

export const sapi = new SakuraApi();

sapi.baseUri = '/api';
sapi.addMiddleware(helmet());
sapi.addMiddleware(bodyParser.json());

addAuthenticationAuthority(sapi, {
  userDbConfig: dbs.user,
  authDbConfig: dbs.authentication,
  defaultDomain: 'default',
  onJWTPayloadInject: onJWTPayloadInject,
  onBeforeUserCreate: onBeforeUserCreate,
  onUserCreated: onUserCreated,
  onResendEmailConfirmation: onResendEmailConfirmation,
  onForgotPasswordEmailRequest: onForgotPasswordEmailRequest,
  onChangePasswordEmailRequest: onChangePasswordEmailRequest
});


function onJWTPayloadInject(payload: any, dbResult: any) {
  // allows you to inject other stuff in to the JWT payload...For example, you might want to look up
  // additional fields from other collections in your app that need to be in the JWT. This is the place to
  // do that. The dbResult (the user's record) so that you can also grab stuff out of there.
  // When you're done modifying the payload to your heart's content, return it as the result of the Promise.
  
  payload.hi = 'Super custom JWT payload property';
  
  return Promise.resolve(payload);
}

function onBeforeUserCreate(res, req, next) {
  // called when a `POST /api/auth/native/` happens (see Create User below). Allows you to
  // do whatever manipulation of the incoming request before proceeding. You may want to
  // implement some kind of safeguard to prevent bots from peppering your server with bogus users.
  //
  // You can also implement some kind of garbage collector that deletes users after a certain amount
  // of time if their email addresses remain unverified.
  next();
}

function onUserCreated(user: any, token: string, req, res): Promise<any> {
  // wire up your outbound email that sends the email verification token to the
  // user... This should redirect to your servers's email verification endpoint that's added
  // by @sakuraapi/auth-native-authority. For example:
  //    GET localhost:8001/api/auth/native/confirm/R5dtU3y302JZuITtSFrNqdl5Mv0QkOPh3VcfgOG86NVkzb385Q.s3RGM0FA0rSfFknjZTfROg.SClksqzZDersBoC2CfhXkQ
  // When the user clicks on that link, their user record will be updated as having verified email address ownership.
  
  if (req.query.mobile) {
    res.locals.send(200, {welcome: `${user.email}, please check your email.`});
  } else {
    res.redirect('http://some-link-welcoming-the-user-and-giving-them-next-steps-like-checking-their-email');
  }
  
  return Promise.resolve();
}


function onResendEmailConfirmation(user: any, token: string, req, res): Promise<any> {
  // add logic here to resend the link with the email verification token
  return Promise.resolve();
}


function onForgotPasswordEmailRequest(user: any, token: string, req, res): Promise<any> {
  // send an email with a forgot password token
  // This email shoudl direct them to a link on your site or a deep link into your app that receives the token then puts it
  // to your server with the token and {"password":"123"} as the body. For example:
  //     PUT localhost:8001/api/auth/native/reset-password/WTtK-g3k-NbikCYxbQ9n97jA.9cXEoJNoMBcxWAGMlB7x4g.x89KcHr0bp-tYkZobbZ10A
  return Promise.resolve();
}

function onChangePasswordEmailRequest(user: any, req, res): Promise<any> {
  // send an email after successful password change
}

```

# Config
Sample:
```{
     "server": {
       "address": "127.0.0.1",
       "port": 8001
     },
     "authentication": {
       "native": {
         "bcryptHashRounds": 12,
         "create": {
           "acceptFields": {
             "firstName": "fn",
             "lastName": "ln",
             "phone": "ph"
           }
         }
       },
       "jwt": {
         "key": "%o9*rMlaU#nm*1m%x!8FSvnqil#$#wsk",
         "issuer": "profile.someserver.org",
         "exp": "48h",
         "fields": {
           "fn": "firstName",
           "ln": "lastName",
           "_id": "id"
         },
         "audiences": {
           "donations.someserver.org": "pJ2@Ymf5#3v53%iKj7vY^G#Qdt&mEnBf",
           "reports.someserver.org": "106s2h*29I@vUrhg&toDpLLCltyf0mYl"
         }
       }
     }
   }

```
The `authentication.native` section defines how `@sakuraapi/auth-native-authority` behaves.

* `bcryptHashRounds` is how many hash rounds bcrypt should go through when hashing the user's password
* `create` handles field mapping of custom fields. For example, the body on user creation is expected to contain a `firstName` field and it will be mapped to the user's record as `fn`. 

The `jwt` section defines how the JWT token is generated. The key is the main private AES-256 key for the token used by your Authentication Authority (All keys should be 32 characters long, and highly complex with high entropy. Consider using LastPass to generate these keys).

* `issuer` is the JWT identification for the authentication authority server (i.e., the server that implements this plugin).
* `exp` is how long the token should be good for before it is considered expired
* `fields` are additional fields from the user's document that should be included in the JWT token
* `audiences` are the target servers that should be included in the resulting token dictionary.

Supporting multiple audiences allows the authentication authority (the issuer) to serve multiple other micro-services without needing those micro-services to share secrets with each other. They only have to trust the issuer.

# Token Dictionary
The following is returned upon successful login (based on the sample configuration above):
```
{
  "token": {
    "profile.someserver.org": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imdlb3JnZV93YXNoaW5ndG9uQHdoaXRlaG91c2UuZ292IiwiZG9tYWluIjoiZGVmYXVsdCIsImZpcnN0TmFtZSI6Ikdlb3JnZSIsImxhc3ROYW1lIjoiV2FzaGluZ3RvbiIsImlkIjoiNTkzMWIyNTE4NjVlNWI4Mjk4YmQ1ZmZmIiwiaXNzU2lnIjoiYzc1N2FlNTkwNjFlNjczN2YyYjIwMjVmOGQ3NThkNTVjNGY4ZjY1YjdhZjdkMjA4Y2EyZjlmZWZjZDMxYmJhOCIsImlhdCI6MTQ5NjQyOTE4OSwiZXhwIjoxNDk2NjAxOTg5LCJhdWQiOiJwcm9maWxlLnNvbWVzZXJ2ZXIub3JnIiwiaXNzIjoicHJvZmlsZS5zb21lc2VydmVyLm9yZyIsImp0aSI6IjA1ZDQ0MTc2LTM0MmUtNDdjNi05ZDNiLWMwZWZjNzIxZTc2OSJ9.zryHww9R8y68jiRqSbf1OOYq88CQ69UsuAlwKWjuftk",
    "donations.someserver.org": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imdlb3JnZV93YXNoaW5ndG9uQHdoaXRlaG91c2UuZ292IiwiZG9tYWluIjoiZGVmYXVsdCIsImZpcnN0TmFtZSI6Ikdlb3JnZSIsImxhc3ROYW1lIjoiV2FzaGluZ3RvbiIsImlkIjoiNTkzMWIyNTE4NjVlNWI4Mjk4YmQ1ZmZmIiwiaXNzU2lnIjoiYzc1N2FlNTkwNjFlNjczN2YyYjIwMjVmOGQ3NThkNTVjNGY4ZjY1YjdhZjdkMjA4Y2EyZjlmZWZjZDMxYmJhOCIsImlhdCI6MTQ5NjQyOTE4OSwiZXhwIjoxNDk2NjAxOTg5LCJhdWQiOiJkb25hdGlvbnMuc29tZXNlcnZlci5vcmciLCJpc3MiOiJwcm9maWxlLnNvbWVzZXJ2ZXIub3JnIiwianRpIjoiMDVkNDQxNzYtMzQyZS00N2M2LTlkM2ItYzBlZmM3MjFlNzY5In0.weoMYNFXQ3skhVXCFzOYDFtFMXDyNWhuyVPtGZIlRfs",
    "reports.someserver.org": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imdlb3JnZV93YXNoaW5ndG9uQHdoaXRlaG91c2UuZ292IiwiZG9tYWluIjoiZGVmYXVsdCIsImZpcnN0TmFtZSI6Ikdlb3JnZSIsImxhc3ROYW1lIjoiV2FzaGluZ3RvbiIsImlkIjoiNTkzMWIyNTE4NjVlNWI4Mjk4YmQ1ZmZmIiwiaXNzU2lnIjoiYzc1N2FlNTkwNjFlNjczN2YyYjIwMjVmOGQ3NThkNTVjNGY4ZjY1YjdhZjdkMjA4Y2EyZjlmZWZjZDMxYmJhOCIsImlhdCI6MTQ5NjQyOTE4OSwiZXhwIjoxNDk2NjAxOTg5LCJhdWQiOiJyZXBvcnRzLnNvbWVzZXJ2ZXIub3JnIiwiaXNzIjoicHJvZmlsZS5zb21lc2VydmVyLm9yZyIsImp0aSI6IjA1ZDQ0MTc2LTM0MmUtNDdjNi05ZDNiLWMwZWZjNzIxZTc2OSJ9.ZHf6oAIgt7IbGu4nQ5VputBl8lwvfCSjAdEdxLsD1KY"
  }
}
```
Each audience (and the issuer itself) gets its own signed JWT. For example, the `profile.someserver.org` token would be signed by the issuer key and would contain the following payload based on the sample configuration above:
```
{
  "email": "george_washington@whitehouse.gov",
  "domain": "default",
  "firstName": "George",
  "lastName": "Washington",
  "id": "5931b251865e5b8298bd5fff",
  "issSig": "c757ae59061e6737f2b2025f8d758d55c4f8f65b7af7d208ca2f9fefcd31bba8",
  "iat": 1496429189,
  "exp": 1496601989,
  "aud": "profile.someserver.org",
  "iss": "profile.someserver.org",
  "jti": "05d44176-342e-47c6-9d3b-c0efc721e769"
}
```
The `issSig` is the payload signed by the issuer so that if an audience server needs to send a token to the issuer (the authentication authority), the issuer can verify that the token hasn't been modified since being issued by the issuer since the audience server has its own private key and could modify the package and since the issue cannot trust that the audience server isn't compromised.

You can read more about JWT here: https://jwt.io/.

Remember, JWT is base64 encoded, so you can decode it in your client and grab whatever fields your need. If you 
need to communicate encrypted data that the client doesn't have access to, use the `onJWTPayloadInject` to modify
the payload fields appropriately (e.g., AES encrypt some value).

# Endpoints
Assuming your have a `sapi.baseUri = '/api';`
## Login
POST /api/auth/native/login
Body:
```
{
	"email":"hi@sakuraapi.com",
	"password": "SomethingSuperSecure"
}
```

## Create User
POST /api/auth/native/
```
{
	"email":"george_washington@whitehouse.gov",
	"domain": "default",
	"password": "IuIwCmKyKVZdj&400IlSW&cyzd0EVZE2",
	"firstName": "George",
	"lastName": "Washington",
	"phone": "(202) 456-1111"
}
```

## Verify an email address
GET /api/auth/native/confirm/mqfKg-vXfl6jfHmrgN4CvzgiYZ-5QVbf_WEiHWQz-7mpGLvgdg.tcay86ro6kCH_PZsK3V1VQ.gomOs3y4PGdN1a9YKs3Igw

Assuming the token received was `mqfKg-vXfl6jfHmrgN4CvzgiYZ-5QVbf_WEiHWQz-7mpGLvgdg.tcay86ro6kCH_PZsK3V1VQ.gomOs3y4PGdN1a9YKs3Igw`.

## Forgot Password
PUT /api/auth/native/forgot-password
Body:
```
{
	"email":"george.washington@whitehouse.gov"
}
```

## Reset Forgotten Password
PUT /api/auth/native/reset-password/WTtK-g3k-NbikCYxbQ9n97jAf17VVZJ98oz2V96AknCZ1cr1k3_tUAtwyDNftRoTT07e0AW-LAdj91Mb.9cXEoJNoMBcxWAGMlB7x4g.x89KcHr0bp-tYkZobbZ10A
Body:
```
{
	"password":"someSuperSecureNewPassword"
}
```

Assuming the "forgot password" token received was `WTtK-g3k-NbikCYxbQ9n97jAf17VVZJ98oz2V96AknCZ1cr1k3_tUAtwyDNftRoTT07e0AW-LAdj91Mb.9cXEoJNoMBcxWAGMlB7x4g.x89KcHr0bp-tYkZobbZ10A`

## Change Passsword
PUT /api/auth/native/change-password/
Body: 
```
{
	"email":"george.washington@whitehouse.gov",
	"currentPassword": "123",
	"newPassword": "321"
}
```

# Indexing
`@sakuraapi/auth-native-authority` relies on two collections:

* A user collection (defined by `userDbConfig` in the example above) that stores documents representing users
* An authentication collection (defined by `authDbConfig` in the example above) that stores all active tokens that have been issued.

Both collections are supplied by the integrator (you). As the integrator, you should ensure that proper indexing is applied to these collections. For example, if you turn on domain support, you should ensure a unique index for the user's email address and the domain. For the authentication collection, you should set the TTL index to automatically delete documents older than the TTL (or some period longer than that if you want to keep them around for a while for short-term auditing purposes). 

# Contributions
[![CLA assistant](https://cla-assistant.io/readme/badge/sakuraapi/auth-native-authority)](https://cla-assistant.io/sakuraapi/auth-native-authority)

See: [CONTRIBUTING](CONTRIBUTING) for details.