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/security
There is no built-in user. Give the login a user source in one of two ways:
One user in cell.yml. password is a hash made by the PasswordEncoder, never the plain password:
backend:
cell:
security:
username: admin
password: pbkdf2-sha256$600000$<salt>$<key>
Make a hash with the encoder of the version you deploy, for example:
// 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.
Your own UserService, for users stored in a database:
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).
| 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).
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.
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.
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 |