@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.
npm install @celljs/securityUsers
There is no built-in user. Give the login a user source in one of two ways:
One user in
cell.yml.passwordis a hash made by thePasswordEncoder, never the plain password:yamlbackend: 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 (injectPasswordEncoder), not with the script above.Your own
UserService, for users stored in a database:tsimport { 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
| Way | Request | Result |
|---|---|---|
| Form | POST /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 |
| Basic | Authorization: Basic … on any request, e.g. curl -u admin:secret http://localhost:3000/users | The request itself is authenticated and continues to the resource; wrong credentials 401 |
| Logout | POST /logout | 302 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
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):
| Key | Default | |
|---|---|---|
username, password | — | The single user of the default UserService; password is a hash |
loginUrl, loginMethod | /login, POST | Form login endpoint |
usernameKey, passwordKey | username, password | Field names of the form login |
loginSuccessUrl | / | Where a form login redirects |
logoutUrl, logoutMethod, logoutSuccessUrl | /logout, POST, the login page | Logout |
basic.enabled, basic.realm | false, realm | HTTP Basic |
passwordEncoder.secret | — | Optional pepper mixed into new hashes |