Feathers UCAN Authentication
feathers-ucan is an extension for FeathersJS that integrates User Controlled Authorization Networks (UCAN) tokens into the existing JWT authentication system. Currently at version 0.1.45, the library is in an early, active development stage, with the README indicating that UCAN concepts are still emerging and the implementation is tailored to specific project needs, suggesting an iterative release cadence. Its key differentiators include the ability to register a `UcanStrategy` with the Feathers authentication service, utilities for generating UCAN capabilities (`genCapability`), and a `CoreCall` class designed to manage and propagate 'core' authentication parameters across internal service calls, aiming to optimize performance by avoiding redundant authentication checks. The package strives to remain unopinionated while providing a structured way to leverage UCAN's decentralized authorization model within a Feathers application.
Common errors
-
Error: No authentication strategy 'ucan' registered.
cause The `UcanStrategy` was not registered with the Feathers `AuthenticationService` or was registered incorrectly.fixEnsure you have `app.use('/authentication', new AuthenticationService(app));` and then `app.service('authentication').register('ucan', new UcanStrategy());` in your Feathers configuration. -
FeathersError: NotAuthenticated - UCAN verification failed: Invalid audience
cause The `aud` (audience) field in the provided UCAN token does not match the `authentication.ucan_aud` configuration value set in your Feathers application.fixVerify that the UCAN token being sent by the client has an `aud` field that precisely matches the `authentication.ucan_aud` configuration in your Feathers `default.json` or `app.set('authentication', { ... });`. -
TypeError: Cannot read properties of undefined (reading 'client_ucan') OR Cannot read properties of undefined (reading 'ucan_aud')
cause The `authentication` configuration in your Feathers application is missing the `client_ucan` or `ucan_aud` properties, which are required by `feathers-ucan`.fixAdd both `client_ucan` and `ucan_aud` to your Feathers `authentication` configuration, typically in `config/default.json` or by calling `app.set('authentication', { ... });`.
Warnings
- breaking This package is in a pre-1.0 state (0.1.x), meaning API changes, including breaking ones, may occur without strictly adhering to SemVer conventions. The README itself notes UCAN tokens are 'still emerging'.
- gotcha The `core` params mechanism described in the README (e.g., `client_ucan`, `ucan_aud`, and the `CoreCall` class) is an internal optimization for managing authentication context. Mismanaging these parameters, especially across internal service calls, can lead to authentication failures or loss of user context.
- gotcha UCAN tokens are unopinionated, and their effective use, especially regarding capabilities (`caps`), requires careful design specific to your application's authorization model. Simply verifying a UCAN token does not automatically grant appropriate access; capabilities must be checked.
Install
-
npm install feathers-ucan -
yarn add feathers-ucan -
pnpm add feathers-ucan
Imports
- AuthService
const { AuthService } = require('feathers-ucan');import { AuthService } from 'feathers-ucan'; - UcanStrategy
import UcanStrategy from 'feathers-ucan';
import { UcanStrategy } from 'feathers-ucan'; - genCapability
import { GenCapability } from 'feathers-ucan';import { genCapability } from 'feathers-ucan';
Quickstart
import { feathers } from '@feathersjs/feathers';
import express from '@feathersjs/express';
import { UcanStrategy } from 'feathers-ucan';
import { AuthenticationService, LocalStrategy, expressOauth } from '@feathersjs/authentication';
import { NotAuthenticated } from '@feathersjs/errors';
interface AppConfig {
authentication: {
secret: string;
entity: string;
service: string;
authStrategies: string[];
jwtOptions: {
header: { typ: string };
audience: string;
issuer: string;
algorithm: string;
expiresIn: string;
};
client_ucan?: string;
ucan_aud?: string;
};
}
const app = express(feathers());
// Minimal configuration for authentication service
app.set('authentication', {
secret: process.env.AUTH_SECRET ?? 'super-secret-secret-key-insecure-for-production',
entity: 'user',
service: 'users',
authStrategies: ['ucan', 'local'],
jwtOptions: {
header: { typ: 'access' },
audience: 'https://your-app.com',
issuer: 'feathers',
algorithm: 'HS256',
expiresIn: '1d'
},
// feathers-ucan specific config (defaults for example)
client_ucan: 'did:key:example-client',
ucan_aud: 'did:key:example-app-server'
} as AppConfig['authentication']);
// Register the standard Feathers authentication service
app.use('/authentication', new AuthenticationService(app));
// Register UCAN and Local strategies
const authService = app.service('authentication') as AuthenticationService; // Cast for types
authService.register('ucan', new UcanStrategy());
authService.register('local', new LocalStrategy());
// Enable Feathers Express middleware
app.configure(express.rest());
app.configure(expressOauth());
// Basic user service for local strategy (not strictly needed for UCAN but completes the example)
app.use('/users', {
async create(data: any) { return { id: 1, email: data.email, password: data.password }; },
async get(id: string) {
if (id === '1') return { id: 1, email: 'test@example.com' };
throw new NotAuthenticated('User not found');
}
});
// Add authentication hooks
app.service('authentication').hooks({
before: {
create: [
AuthenticationService.hooks.authenticate(['ucan', 'local'])
]
}
});
app.listen(3030).on('listening', () => {
console.log('Feathers application listening on http://localhost:3030');
console.log('Try authenticating with a UCAN token or local strategy.');
console.log('Example: POST to http://localhost:3030/authentication with { strategy: "local", email: "test@example.com", password: "password" }');
});