Stone.jsDocs
Paradigm

Extensions

Auth

Authentication asks one question: who is calling. @stone-js/auth answers it statelessly, verifying a JWT or OAuth token at the boundary, so there is no session store to run and the same guard works on Node, on the edge, and in an agent call.

#Install

terminalbash
npm i @stone-js/auth

#Configure the signing strategy

Auth is enabled from a @Configuration() that merges the auth blueprint (its service provider and the kernel middleware that verifies the Bearer token) and sets how tokens are signed and verified. Use a shared HMAC secret for symmetric JWT, or a publicKey/jwksUri to verify tokens minted by an external identity provider. Read secrets from the environment, never hard-code them.

app/configurations/AuthConfiguration.tsts
import { getString } from '@stone-js/env'
import { authBlueprint } from '@stone-js/auth'
import { Configuration, IBlueprint, IConfiguration } from '@stone-js/core'

@Configuration()
export class AuthConfiguration implements IConfiguration {
  configure (blueprint: IBlueprint): void {
    blueprint
      .set(authBlueprint)                                    // provider + verify middleware
      .set('stone.auth.secret', getString('JWT_SECRET'))     // HMAC (HS256); or publicKey / jwksUri
      .set('stone.auth.issuer', 'https://your-issuer.example')
      .set('stone.auth.audience', 'your-api')
      .set('stone.auth.ttl', '1h')
  }
}

#Identity at the boundary

The principle

Identity should be established once, at the edge, and carried as context, not re-derived deep in the code. Server-held state ties you to one machine; a token verified at the boundary travels wherever the request does.

In Stone.js

Guards are middleware. requireAuth() rejects anonymous calls with a 401; requireScopes(...) additionally demands OAuth scopes, rejecting a missing one with a 403. The verified principal is then available on the event.

app/Tasks.tsdeclarativeimperative
import { EventHandler, Get, Post } from '@stone-js/router'
import { requireAuth, requireScopes } from '@stone-js/auth'

@EventHandler('/tasks')
export class TaskController {
  @Get('/', { middleware: [requireAuth()] })              // must be authenticated
  list () { return this.tasks.list() }

  @Post('/', { middleware: [requireScopes('tasks:write')] })  // must hold the scope
  create (event) { return this.tasks.add(event.get('title')) }
}
import { defineEventHandler, defineRoutes } from '@stone-js/router'
import { requireAuth, requireScopes } from '@stone-js/auth'

export const routes = defineRoutes([
  [defineEventHandler(TaskController, 'list'),
    { path: '/tasks', method: 'GET', middleware: [requireAuth()] }],
  [defineEventHandler(TaskController, 'create'),
    { path: '/tasks', method: 'POST', middleware: [requireScopes('tasks:write')] }]
])

#Guards

GuardTypeDescription
requireAuth()() => middlewareRequire a valid token; 401 when anonymous.
requireScopes(...scopes)(...string) => middlewareRequire every listed OAuth scope; 401 when anonymous, 403 when a scope is missing.

#Reading the principal

app/Tasks.tsts
@Get('/mine', { middleware: [requireAuth()] })
mine (event: IncomingHttpEvent) {
  const user = event.get('user')          // the authenticated principal
  return this.tasks.ownedBy(user.id)
}

#Custom strategies

The built-in verification covers JWT and OAuth. When you need something else, an API key, a session, a bespoke provider, implement an Authenticator: it turns a request into a principal (or rejects it), and the same requireAuth/requireScopesguards work on top of it unchanged.

app/ApiKeyAuthenticator.tsts
import { Authenticator } from '@stone-js/auth'

export class ApiKeyAuthenticator extends Authenticator {
  async authenticate (event: IncomingHttpEvent) {
    const key = event.getHeader('x-api-key')
    const user = await this.keys.resolve(key)
    if (user === undefined) throw new AuthenticationError('Invalid API key')  // -> 401
    return user                                    // becomes event.get('user')
  }
}

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)