Git Product home page Git Product logo

libauth.js's Introduction

libauth.js

Modern, OpenID Connect compatible authentication.

// ...
let LibAuth = require("libauth");
let libauth = LibAuth.create(issuer, privkey, { cookiePath: "/api/authn/" });

// ...
app.post("/api/authn/session/credentials", setCookieByCredentials);
app.post("/api/authn/session/id_token", sendIdTokenBySession);
app.post("/api/authn/access_token", sendAccessTokenByIdToken);
app.delete("/api/authn/session", revokeCookieAndIdToken);

app.use("/.well-known/openid-configuration", libauth.wellKnownOidc());
app.use("/.well-known/jwks.json", libauth.wellKnownJwks());

Features

  • Standards-based session management (localStorage not necessary)
    • Long-lived refresh tokens (default 30d)
    • Long-lived session cookies (default 30d)
    • Short-lived id & access tokens (default 1d, 15m)
    • Logout (expire refresh cookie)
  • Supports common login strategies
    • Credentials-based login (HTTP Basic Auth)
    • OIDC (OpenID Connect, OAuth2) login (Google Sign In, etc)
    • Challenge-Response (Magic Link)
  • Idiomatic, Composable Express.js Routing
  • Email & SMS Verification flow (bring-your-own-mailer)
  • No need for localStorage!

Table of Contents

Installation

Install Node.js:

curl https://webinstall.dev/node@v16 | bash
export PATH="$HOME/.local/opt/node/bin:$PATH"

Install libauth:

npm install --save libauth@v0

Note: The v1 API is not yet locked. Some names may change. Any changes from v0.90.x on will be noted in migration notes.

Philosophy

The goal of LibAuth is to minimize magic (anything difficult to understand or configure), and maximize control, without sacrificing ease-of-use, convenience, or security.

To do this we require more copy-and-paste boilerplate than other auth libraries - with the upside is that it's all just normal, easy-to-replace middleware - hopefully nothing unexpected or constraining.

You'll also notice that we try to use the proper, official technical language rather than potentially ambiguous sugar-coated terms (for example: 'cookie' or 'token' when specificity is required, or 'session' when it could be either).

Usage

Prerequisites: Generate Secrets

There's a few keys, secrets, and salts that you'll need. Here's how you can generate them:

Automatic

You can generate a .env with the required secrets all at once:

npx libauth@v0 envs ./.env

.env:

COOKIE_SECRET=xxxx
MAGIC_SALT=xxxx
PRIVATE_KEY='{"d": "xxxx"}'

Option B: Manual

Or you can generate them one-by-one with the libauth commands:

npx libauth@v0 privkey
npx libauth@v0 rnd

which are the same as running the following:

./node_modules/libauth/bin/libauth.js privkey
./node_modules/libauth/bin/libauth.js rnd

Each of these should be included in your .env file:

  1. Generate the PRIVATE_KEY:
    echo "PRIVATE_KEY='$(
     npx libauth@v0 privkey
    )'" >> .env
  2. Generate the COOKIE_SECRET:
    echo "COOKIE_SECRET=$(
     npx libauth@v0 rnd 16
    )" >> .env
  3. Generate the MAGIC_SALT:
    echo "MAGIC_SALT=$(
     npx libauth@v0 rnd 16
    )" >> .env

You could also use keypairs and openssl rand -base64 16.

1. Top-Level Routes

"use strict";

let FsSync = require("fs");

let issuer = process.env.BASE_URL || `http://localhost:${process.env.PORT}`;
let privkey = JSON.parse(FsSync.readFileSync("./key.jwk.json", "utf8"));

let bodyParser = require("body-parser");
app.use("/api/authn", bodyParser.json({ limit: "100kb" }));

let cookieParser = require("cookie-parser");
let cookieSecret = process.env.COOKIE_SECRET;
app.use("/api/authn/session", cookieParser(cookieSecret));

let authRoutes = require("./auth-routes.js").create(issuer, privkey, {
  cookiePath: "/api/authn/session/",
});

app.post("/api/authn/session/credentials", authRoutes.setCookieByCredentials);
app.post("/api/authn/session/id_token", authRoutes.sendIdTokenBySession);
app.delete("/api/authn/session", authRoutes.revokeCookieAndIdToken);

app.post("/api/authn/access_token", authRoutes.sendAccessTokenByIdToken);

app.get("/.well-known/openid-configuration", authRoutes.wellKnownOidc);
app.get("/.well-known/jwks.json", authRoutes.wellKnownJwks);

module.exports = app;
"use strict";

let AuthRoutes = module.exports;

let LibAuth = require("libauth");

AuthRoutes.create = function () {
  let authRoutes = {};

  let libauth = LibAuth.create(issuer, privkey, {
    cookiePath: "/api/authn/",
    /*
    refreshCookiePath: "/api/authn/",
    accessCookiePath: "/api/assets/",
    */
  });

  // Create session by login credentials
  AuthRoutes.setCookieByCredentials = [
    libauth.readCredentials(),
    MyDB.getUserClaimsByPassword,
    libauth.newSession(),
    libauth.initClaims(),
    libauth.initTokens(),
    libauth.initCookie(),
    MyDB.expireCurrentSession,
    MyDB.saveNewSession,
    libauth.setCookieHeader(),
    libauth.sendTokens(),
  ];

  // Refresh ID Token via Session
  AuthRoutes.sendIdTokenBySession = [
    libauth.requireCookie(),
    MyDB.getUserClaimsBySub,
    libauth.initClaims({ idClaims: {} }),
    libauth.initTokens(),
    libauth.sendTokens(),
  ];

  // Exchange Access Token via ID Token
  AuthRoutes.sendAccessTokenByIdToken = [
    libauth.requireBearerClaims(),
    MyDB.getUserClaimsBySub,
    libauth.initClaims({ accessClaims: {} }),
    libauth.initTokens(),
    libauth.sendTokens(),
  ];

  // Logout (delete session cookie)
  AuthRoutes.revokeCookieAndIdToken = [
    libauth.readCookie(),
    MyDB.expireCurrentSession,
    libauth.expireCookie(),
    libauth.sendOk({ success: true }),
    libauth.sendError({ success: true }),
  ];

  AuthRoutes.wellKnownOidc = libauth.wellKnownOidc();
  AuthRoutes.wellKnownJwks = libauth.wellKnownJwks();

  return authRoutes;
};
"use strict";

require("dotenv").config({ path: ".env" });

let Http = require("http");
let Express = require("express");

let server = Express();
let app = new Express.Router();

let port = process.env.PORT || 3000;
Http.createServer(server).listen(port, function () {
  /* jshint validthis:true */
  console.info("Listening on", this.address());
});

Handling the following strategies:

  • oidc - ex: Facebook Connect, Google Sign In, Microsoft Live
  • credentials - bespoke, specified by you
    • Username / Password
    • API Key
  • challenge - a.k.a. "verification email" or "Magic Link" ( or SMS code)
  • refresh - to refresh an id_token via refresh token cookie
  • exchange - to exchange an id_token for an access_token
    • JWK

Node API

Errors

Name Status Message (truncated)
E_CODE_NOT_FOUND 404 That verification code isn't valid. It might ...
E_CODE_INVALID 400|403 That verification code isn't valid. It might ...
E_CODE_REDEEMED 400 That verification code has already been used ...
E_CODE_RETRY 400 That verification code isn't correct. It may ...
E_OIDC_UNVERIFIED_IDENTIFIER 400 You cannot use the identifier associated with...
E_SESSION_INVALID 400 Missing or invalid cookie session. Please log...
E_SUSPICIOUS_REQUEST 400 Something suspicious is going on - as if ther...
E_SUSPICIOUS_TOKEN 400 Something suspicious is going on - the given ...
E_DEVELOPER_ERROR 422 Oops! One of the programmers made a mistake. ...
" -> WRONG_TOKEN_TYPE 422 the HTTP Authorization was not given in a sup...
" -> MISSING_TOKEN 401 the required authorization token was not prov...

Glossary

Term Meaning
JWS A decoded JWT (non-compact JWS), or JSON Web Signature
JWT A compact (or encoded) JWS, or JSON Web Token
id_token A JWT with information about the user, such given_name
access_token A JWT with information about an account or resource
refresh_token A long-lived JWT, stored in a session cookie (or config file)
amr The list of methods used for Multi-Factor Authentication
acr For specifying LoA Profiles (mostly useless)

Design Decisions

  • directed flow of data
    • libauth passes data to you through req.authn via middleware
    • You pass data to libauth by POST or by calling functions

Resources

Live Code Project

This code was written live, in front of a combined YouTube & Twitch audience.

If you want to see all 40+ hours of painstaking coding... here ya go:

https://www.youtube.com/playlist?list=PLxki0D-ilnqYmidRxvrQoF2jX67wH5OS0

libauth.js's People

Contributors

coolaj86 avatar wmerfalen avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.