← back to projects list

nyckel

Authentication for hybrid web apps

A fairly extensive Auth0-interfacing collection of TypeScript modules that allow authentication with Auth0 throughout a hybrid web app, consisting of both browser- and server rendered frontends as well as backends. Authentication is handled as distributed sessions containing JWTs that are cross-validated when permissions are necessary, to prevent stale tokens.

Details

Nyckel is a collection of TypeScript modules for server-side/client-side hybrid web apps, providing both session support and authentication support using Auth0. The main goal behind this project was to simplify handling of user authentication in web projects and make a fully tested abstraction of what is typically quite complex code. In the end, this is achieved via a public-facing API that is very simple to use and hopefully is perceived as non-magic and intuitive.

The packages

@nyckel/express-middleware

This package is an express middleware, combining all of the other packages into a fully fledged authentication solution under some specific routes:

/auth/login - an user-facing endpoint from which authentication is initiated

/auth/logout - an user-facing endpoint from which the current session is ended and user is properly logged out (via Auth0)

/auth/loggedout - an endpoint which serves as the callback location from an Auth0 logout

/auth/callback - an endpoint which serves as the callback location from an Auth0 authentication attempt

@nyckel/http-middleware

This package provides utilities for accessing the current session and user of an HTTP request. For instance, the user can be provided as the following type:

type UserInRequest = {
  get(): Promise<
    | ({
        accessTokenData: DecodedAccessToken;
        idTokenData: DecodedIdToken;
        accessToken: string;
        idToken: string;
        expiresIn: number;
      })
    | null
  >;
  userInfo(): Promise<any>;
  fullUserData(): Promise<any>;
  roles(): Promise<any>;
  permissions(): Promise<any>;
}

@nyckel/sessions

This package provides a simple session manager using Redis as a backing store, with support for locking a single session during important reads and writes. All sessions are encrypted and stored under hashed keys, resulting in that only valid client requests can access the session data.

@nyckel/authentication

This package provides the interfacing with the Auth0 authentication API and is used by the other packages to handle user-facing authentication.

The APIs of this package are mainly for use in @nyckel/http-middleware, which provides some helper functions for user authentication.

@nyckel/management

This package provides the interfacing with the Auth0 management API and is used by the other packages server-side to verify roles and permissions.

The APIs of this package are mainly for use in @nyckel/http-middleware, which provides some helper functions for checking roles and permissions.

Usage example

import express from "express";
import cookieParser from "cookie-parser";

import { createGlobalAuthenticationConfig } from "@nyckel/authentication";
import { createSessionManager } from "@nyckel/sessions";
import { nyckelExpressMiddleware, asyncHandler } from "@nyckel/express-middleware";

const app = express();
app.use(cookieParser());

const authConfig = createGlobalAuthenticationConfig(/* ... omitted ... */);
const authManagementConfig = createGlobalAuthenticationConfig(/* ... omitted ... */);
const sessionManager = createSessionManager(/* ... omitted ... */);
const authMiddleware = nyckelExpressMiddleware(/* ... omitted ... */);

app.use(authMiddleware);
app.get(
  "/",
  // asyncHandler calls 'next(err)' if the promise rejects
  asyncHandler(async (req, res, next) => {
    const user = await req.user.get();
    if (user != null) {
      res.write("<p>" + Math.floor(user.expiresIn / 1000) + "</p>");
      res.write("<a href='/auth/logout'>log out</a>");
    } else {
      res.write("<a href='/auth/login'>log in</a>");
    }
    res.write("<h3>req.user.get()</h3>");
    res.write("<pre>" + JSON.stringify(user, null, "  ") + "</pre>");
    res.write("<h3>req.user.userInfo()</h3>");
    res.write("<pre>" + JSON.stringify(await req.user.userInfo(), null, "  ") + "</pre>");
    res.write("<h3>req.user.fullUserData()</h3>");
    res.write("<pre>" + JSON.stringify(await req.user.fullUserData(), null, "  ") + "</pre>");
    res.write("<h3>req.user.roles()</h3>");
    res.write("<pre>" + JSON.stringify(await req.user.roles(), null, "  ") + "</pre>");
    res.write("<h3>req.user.permissions()</h3>");
    res.write("<pre>" + JSON.stringify(await req.user.permissions(), null, "  ") + "</pre>");
    res.end();
  })
);

app.listen(8000);

Part of activity

Developer, designer & librarian
Space Countdown
Developing and designing a data-driven web project which has become an extensive and detailed archive of SpaceX's past missions as well as a helpful tool to plan for watching the next launch. As part of this I also maintain the extensive data set used by the site.

Tags

  • Authentication

Technologies

  • TypeScript
  • Auth0
  • Jest