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

@lcdev/auth

v1.1.1

Published

Common authentication mechanism for koa applications

Downloads

4

Readme

Launchcode Auth Package

Common authentication mechanism for backend services. Uses a straightforward JWT implementation with a token blacklist for logout/invalidating.

yarn add @lcdev/auth@VERSION

If you're not familiar, you should be at least familiar with:

In short:

  • We use JWTs for authenticating every user's identity
    • They are stateless, so we don't need to store any sessions ourselves
    • We can embed data into JWTs, avoiding costly database calls for user info
    • We can trust data embedded into JWTs, because we're signing them with secret keys
  • We blacklist individual JWTs based off of a uuid in the JWT data
    • A more complex blacklisting scheme is easily possible (like by JWT data, eg. User ID)

Quick Start

There are two steps to integration. Login and normal authentication. We can start with the common one, authentication.

import { createAuthStrategy, Cache } from '@lcdev/auth';
import { createAuthCache } from '@lcdev/auth-redis-blacklist';

// here, we define the type of what we expect to be in all JWT tokens
interface JWT {
  // it's very typical to see things like userID, user role, and maybe some preferences here
  userID: number;
}

const cache = createAuthCache(redisConnection, 'some-token-blacklisting-key');

// most of this would normally come from app-config
const config = {
  expiry: 60, // seconds
  secrets: ["private-signing-key"], // this is the secret we sign tokens with!
  blacklistClearingInterval: 120, // seconds, how often do we go back and clear out old blacklist entries

  // optional: issuer, audience, debug, etc.
};

const auth = createAuthStrategy(cache, config);

So now we have this 'auth' object. Normally, we'd centralize the different logic here (eg. cache/redis, config, jwt definition). Once we've created the strategy, we can use it in routes to protect them.

// assuming you're using @lcdev/router
route({
  path: '/protected-route',
  method: HttpMethod.GET,
  // this is the key point - `authenticate` is middleware that will prevent bad or missing tokens from going through
  middleware: [auth.authenticate()],
  async action(ctx) {
    // in here, we get 'token', 'jwt' in ctx.state if need be (you'll need to cast `jwt` into your JWT type)
    return { sensitiveInfo: true };
  },
}),

How are tokens passed? An HTTP Authorization header with Bearer {tokenHere}. So when a client calls the API, it needs to include that header. There are config options for cookies and others, if you require.

Not so hard right? Only last part is user login, which is of course a tiny bit more involved.

Instead of createAuthStrategy, we'll use createAuthLoginStrategy. It's a superset of createAuthStrategy, so feel free to reuse the strategy for both use cases.

import { compare } from 'bcrypt';
import { createAuthLoginStrategy, FetchLogin, CheckLogin } from '@lcdev/auth';

const {
  authenticate,
  login,
  logout,
  newToken,
} = createAuthLoginStrategy<JWT>(
  cache,
  config,
  // fetch the user by username here
  async (username) => {
    const user = await User.query().where({ username });

    if (!user) {
      throw { message: 'no user found', status: 401 };
    }

    return user;
  },
  // compare the given plaintext password with your stored hash
  async (user, password) => {
    return compare(password, user.password);
  },
);

A login and logout route would look pretty similar to our earlier example.

route({
  path: '/login',
  method: HttpMethod.POST,
  // login verifies the user identity, and newToken generates a normal JWT
  middleware: [login(), newToken()],
  async action(ctx) {
    const { token, expiry } = ctx.state;

    // token is the raw JWT string, expiry is a date
    return { token, expiry };
  },
}),
route({
  path: '/logout',
  method: HttpMethod.POST,
  // the logout middleware adds this token to the blacklist, until it expires
  middleware: [logout()],
  async action(ctx) {
    return {};
  },
}),

Another other useful pattern is refreshing a given token:

route({
  path: '/refresh',
  method: HttpMethod.POST,
  // authenticate verifies the user identity, and newToken generates a new JWT
  middleware: [authenticate(), newToken()],
  async action(ctx) {
    const { token, expiry } = ctx.state;
    return { token, expiry };
  },
}),

Of course, it's useful to see a real example.