Skip to content

@celljs/security ​

Authentication and authorization for Cell backends: form login, HTTP Basic, logout, @Authenticated() / @Anonymous() / @PreAuthorize() on controllers, and password hashing. Install it and it is active; no registration is needed.

bash
npm install @celljs/security

Users ​

There is no built-in user. Give the login a user source in one of two ways:

  1. One user in cell.yml. password is a hash made by the PasswordEncoder, never the plain password:

    yaml
    backend:
      cell:
        security:
          username: admin
          password: pbkdf2-sha256$600000$<salt>$<key>

    Make a hash with the encoder of the version you deploy, for example:

    js
    // hash.mjs — node hash.mjs 'the password'
    import 'reflect-metadata';
    import { Pbkdf2PasswordEncoder } from '@celljs/security/lib/node/crypto/password/password-encoder';
    console.log(await new Pbkdf2PasswordEncoder().encode(process.argv[2]));

    If you set cell.security.passwordEncoder.secret (a pepper), new hashes depend on it: make them inside the application (inject PasswordEncoder), not with the script above.

  2. Your own UserService, for users stored in a database:

    ts
    import { Component } from '@celljs/core';
    import { User, UserType } from '@celljs/security/lib/common';
    import { UserService, UsernameNotFoundError } from '@celljs/security/lib/node';
    
    @Component({ id: UserService, rebind: true })
    export class DbUserService implements UserService<string, User> {
        async load(username: string): Promise<User> {
            const row = await findUser(username);              // your data access
            if (!row) {
                throw new UsernameNotFoundError(`Could not find: ${username}`);
            }
            return {
                type: UserType.Database, username, password: row.passwordHash, policies: [],
                accountNonExpired: true, accountNonLocked: true, credentialsNonExpired: true, enabled: true
            };
        }
    
        // Optional: lets a login replace an outdated hash (see "Password hashes").
        async updatePassword(user: User, encodedPassword: string): Promise<void> {
            await saveHash(user.username, encodedPassword);
        }
    }

With neither, the application starts and logs a warning that no user can log in (an application may log in with OAuth2 only, or use this package for authorization alone).

Logging in ​

WayRequestResult
FormPOST /login with username and password, as JSON or as a form (cell.security.loginUrl, loginMethod, usernameKey, passwordKey)302 to loginSuccessUrl (or the redirect query parameter) with a session cookie; wrong credentials 401
BasicAuthorization: Basic … on any request, e.g. curl -u admin:secret http://localhost:3000/usersThe request itself is authenticated and continues to the resource; wrong credentials 401
LogoutPOST /logout302 to logoutSuccessUrl

Basic is off by default; turn it on with cell.security.basic.enabled: true. It checks the password on every request, and one check is one PBKDF2 derivation with 600000 iterations (about 45 ms of CPU). Use Basic only for low-frequency machine-to-machine calls; for frequent calls log in once and use the session, or use JWT (@celljs/jwt).

Protecting endpoints ​

ts
import { Controller, Get } from '@celljs/mvc/lib/node';
import { Anonymous, Authenticated } from '@celljs/security/lib/node';

@Controller('users')
@Authenticated()            // requires a logged-in user; anonymous requests get 401
export class UserController {
    @Get()
    list() { return []; }
}

@Controller()
@Anonymous()                // reachable without logging in
export class HomeController {}

@PreAuthorize(el) / @PostAuthorize(el) check an expression before or after the method; @Authorize, @Resource and @Action work with policies.

Password hashes ​

PasswordEncoder (Pbkdf2PasswordEncoder) stores pbkdf2-sha256$<iterations>$<salt>$<key>: PBKDF2-HMAC-SHA256, 600000 iterations, a random 16-byte salt, compared in constant time.

Hashes stored by 4.0.x keep matching. After a successful login (form or Basic), such a hash is replaced by a new one when your UserService implements updatePassword(). The upgrade is one-way: once a user has logged in, an application rolled back to 4.0.x can no longer check that user's password. Hashes made with crypto-js 4.1 or older are not supported.

Configuration ​

Keys under cell.security (defaults in this package's cell.yml):

KeyDefault
username, password—The single user of the default UserService; password is a hash
loginUrl, loginMethod/login, POSTForm login endpoint
usernameKey, passwordKeyusername, passwordField names of the form login
loginSuccessUrl/Where a form login redirects
logoutUrl, logoutMethod, logoutSuccessUrl/logout, POST, the login pageLogout
basic.enabled, basic.realmfalse, realmHTTP Basic
passwordEncoder.secret—Optional pepper mixed into new hashes