Install: @travetto/auth
npm install @travetto/auth
# or
yarn add @travetto/auth
This module provides the high-level backdrop for managing security principals. The goal of this module is to be a centralized location for various security frameworks to plug into. The primary contributions are:
- Standard Types
- Authentication Contract
- Authorization Contract
- Common security-related utilities for
- Checking permissions
- Generating passwords
The module's goal is to be as flexible as possible. To that end, the primary contract that this module defines, is that of the Principal Structure.
Code: Principal Structure
export interface Principal<D = AnyMap> {
/**
* Primary identifier for a user
*/
id: string;
/**
* Date of expiration
*/
expiresAt?: Date;
/**
* Date of issuance
*/
issuedAt?: Date;
/**
* Max age in seconds a principal is valid
*/
maxAge?: number;
/**
* The source of the issuance
*/
issuer?: string;
/**
* Supplemental details
*/
details: D;
/**
* List of all provided permissions
*/
permissions?: string[];
}
As referenced above, a Principal Structure is defined as a user with respect to a security context. This can be information the application knows about the user (authorized) or what a separate service may know about a user (3rd-party authentication).
Code: Authenticator
export interface Authenticator<T = unknown, P extends Principal = Principal, C = unknown> {
/**
* Allows for the authenticator to be initialized if needed
* @param ctx
*/
initialize?(ctx: C): Promise<void>;
/**
* Verify the payload, ensuring the payload is correctly identified.
*
* @returns Valid principal if authenticated
* @returns undefined if authentication is valid, but incomplete (multi-step)
* @throws AppError if authentication fails
*/
authenticate(payload: T, ctx?: C): Promise<P | undefined> | P | undefined;
}
The Authenticator only requires one method to be defined, and that is authenticate
. This method receives a generic payload, and a supplemental context as an input. The interface is responsible for converting that to an authenticated principal.
The JWT module is a good example of an authenticator. This is a common use case for simple internal auth.
Code: Authorizer
export interface Authorizer<P extends Principal = Principal> {
/**
* Authorize inbound principal, verifying it's permission to access the system.
* @param principal
* @returns New principal that conforms to the required principal shape
*/
authorize(principal: Principal): Promise<P> | P;
}
Authorizers are generally seen as a secondary step post-authentication. Authentication acts as a very basic form of authorization, assuming the principal store is owned by the application.
The Authorizer only requires one method to be defined, and that is authorizer
. This method receives an authenticated principal as an input, and is responsible for converting that to an authorized principal.
The Data Modeling Support extension is a good example of an authenticator. This is a common use case for simple internal auth.
Overall, the structure is simple, but drives home the primary use cases of the framework. The goals are:
- Be able to identify a user uniquely
- To have a reference to a user's set of permissions
- To have access to the principal
The AuthUtil provides the following functionality:
Code: Auth util structure
import crypto from 'node:crypto';
import util from 'node:util';
import { AppError, Util } from '@travetto/runtime';
const pbkdf2 = util.promisify(crypto.pbkdf2);
/**
* Standard auth utilities
*/
export class AuthUtil {
/**
* Generate a hash for a given value
*
* @param value Value to hash
* @param salt The salt value
* @param iterations Number of iterations on hashing
* @param keylen Length of hash
* @param digest Digest method
*/
static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256'): Promise<string>;
/**
* Generate a salted password, with the ability to validate the password
*
* @param password
* @param salt Salt value, or if a number, length of salt
* @param validator Optional function to validate your password
*/
static async generatePassword(password: string, salt: number | string = 32): Promise<{ salt: string, hash: string }>;
}
roleMatcher
is probably the only functionality that needs to be explained. The function extends the core allow/deny matcher functionality from Runtime's Util class.
An example of role checks could be:
- Admin
- !Editor
- Owner+Author The code would check the list in order, which would result in the following logic:
- If the user is an admin, always allow
- If the user has the editor role, deny
- If the user is both an owner and an author allow
- By default, deny due to the presence of positive checks