npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sakuraapi/auth-native-authority

v0.6.2

Published

Middleware to support native authentication capabilities

Downloads

6

Readme

Status

|Branch |Status | |-----------|-----------| | Develop |Build Status| | Master |Build Status|

@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": "[email protected]",
  "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":"[email protected]",
	"password": "SomethingSuperSecure"
}

Create User

POST /api/auth/native/

{
	"email":"[email protected]",
	"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":"[email protected]"
}

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":"[email protected]",
	"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

See: CONTRIBUTING for details.