Skip to content
Back to skills

Auth Token

ASecurity

How to use @owlmeans/auth-token — the contracts behind long-lived access tokens (API keys) — the token format and its deployment prefix, the three management routes, the Authorization parsing that Bearer needs, and the client-side carrier guard a CLI or an MCP server authenticates with. Auto-invoked when importing the token entrypoints, the carrier guard, parseAuthorizationHeader, or an access-token type.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 22, 2026
securitytypescriptapidocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add owlmeans/common --skill auth-token --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Auth Token?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Auth Token
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/owlmeans-auth-token/badge)](https://www.skillsdirectory.com/skills/owlmeans-auth-token)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: auth-token
description: How to use @owlmeans/auth-token — the contracts behind long-lived access tokens (API keys) — the token format and its deployment prefix, the three management routes, the Authorization parsing that Bearer needs, and the client-side carrier guard a CLI or an MCP server authenticates with. Auto-invoked when importing the token entrypoints, the carrier guard, parseAuthorizationHeader, or an access-token type.
user-invocable: false
---

# @owlmeans/auth-token

**Layer:** Auth shared
**Install:** `"@owlmeans/auth-token": "^0.1.18-rc.32"` in `dependencies`

The contract half of long-lived access tokens: the record shape, the route declarations, the
format helpers, and one client-side guard that presents a token it was handed. The server half —
the store, the verifying guard and the handlers — is `@owlmeans/server-auth-token`; the management
UI is `@owlmeans/web-auth-token`.

## Key Exports

| Export | Description |
|--------|-------------|
| `makeAuthTokenEntrypoints(opts?)` | The three routes (`list` GET, `create` POST, `revoke` DELETE `/:id`) under a `/tokens` base. `opts`: `parent`, `path`, `guard` |
| `makeTokenCarrierGuard(alias, opts)` | A client `GuardService` that presents one token. `opts.token` is a value or a thunk; `opts.scheme` is `'auth-token'` (default) or `'bearer'`; `opts.onRejected` hears a 401 |
| `parseAuthorizationHeader(header)` | `{ scheme, value }` with the scheme lower-cased, or `null` |
| `isAccessToken(value, prefix)` · `displayOf(token, prefix)` | Whether a value is one of this deployment's tokens; the half of it that may be shown again |
| `CreateAccessTokenSchema` · `AccessTokenParamsSchema` | The ajv body/params filters |
| `authToken` | `{ base, list, create, revoke }` route aliases |
| `GUARD_AUTH_TOKEN` · `AUTH_TOKEN_RESOURCE` · `AUTH_TOKEN_COLLECTION` | `'guard:auth-token'`, `'auth-token:token'`, `'access-token'` |
| `AUTH_TOKEN_DEFAULT_PREFIX` | `'owl_'` — a deployment overrides it |
| `AUTH_TOKEN_SECRET_BYTES` (24) · `AUTH_TOKEN_DISPLAY_LENGTH` (8) | 192 bits of randomness; 8 characters kept for display |
| `AUTH_TOKEN_TOUCH_INTERVAL` (5 min) · `AUTH_TOKEN_MAX_TTL` (366 d) · `AUTH_TOKEN_NAME_MAX` (64) | Tuning |
| `AUTH_TOKEN_SCHEME` · `BEARER_SCHEME` | `'auth-token'`, `'bearer'` — lower-cased, for comparison |
| `AccessTokenRecord`, `AccessTokenView`, `CreateAccessToken`, `IssuedAccessToken`, `AccessTokenList`, `TokenCarrierOptions`, `AuthTokenEntrypointOptions` | Types |

## The prefix is what makes a token CLAIMABLE

A token is `<prefix><base58(24 random bytes)>`. The prefix is per deployment (`vib_`, `acme_`), and
the guard answers `match` only for a value that starts with it — so an access token and an Ed25519
session bearer arrive under the same `Authorization` header without either guard shadowing the
other, and two deployments never mistake each other's credentials for their own.

**`AccessTokenRecord.audience?: string[]`** names the resources a token was issued FOR when it came
through an OAuth grant (`@owlmeans/server-oauth`, RFC 8707 applied at OUR guard — the token is an
opaque secret, not a JWT). Absent on every hand-minted token, which is admitted everywhere its scopes
reach; a non-empty audience is admitted only by a guard whose `resources` intersect it (see
[[server-auth-token]]).

The plaintext exists exactly once, in the create response. What is stored is its hash; what a list
shows forever after is `display` — the prefix plus 8 characters, enough to tell two of your own
tokens apart and far too little to replay.

## Both `AUTH-TOKEN` and `Bearer` are accepted, and that is why this package parses headers itself

A third-party client configured with a URL sends `Bearer` whatever the documentation says. But
`extractAuthToken` (`@owlmeans/auth-common`) compares the prefix against `type.toUpperCase()`, so it
matches `AUTH-TOKEN` and can **never** match `Bearer`. `parseAuthorizationHeader` is the
replacement: it lower-cases the scheme so a caller compares once, takes the first of several headers
a proxy folded together, keeps a value that itself contains spaces, and answers `null` for a scheme
with no value behind it.

## The carrier guard is how a process with no browser authenticates

`authMiddleware` asks every guard an entrypoint declares for `authenticated(req)` and stamps the
first non-null answer onto the header — so registering the carrier under the alias the routes
already name (`DEFAULT_GUARD`, usually) makes an **unchanged route declaration** work from a CLI, an
MCP server or a test.

```typescript
context.registerService(makeTokenCarrierGuard(DEFAULT_GUARD, { token, scheme: 'auth-token' }))
context.registerMiddleware(authMiddleware)
```

There is no session, no refresh and no storage: the token is a long-lived credential the caller was
handed. `opts.token` may be a thunk because a long-running process reads it from an environment
variable or a credentials file and must not cache it past a reconfiguration — or past a sign-in that
has not happened yet.

**The carrier answers `update()`.** On a 401 for a request that presented this guard's bearer,
`@owlmeans/api` calls `service('<alias>').update(undefined)` to clear a browser session; a carrier
with no `update` made that a bare `TypeError` instead of the auth failure already being reported. The
carrier's `update` is a no-op that calls `opts.onRejected` — the hook a credential holder uses to
forget a dead token and sign in again (or to report it, when an operator supplied it by hand).

## The management surface is deliberately not a CRUD

There is no update. A token's scopes and lifetime are fixed at issuance, because a token that can be
widened later is a grant nobody can reason about from the moment it was created.

```typescript
context.registerEntrypoints(makeAuthTokenEntrypoints({ parent: account.base, path: '/tokens' }))
```

Mount it under an account section: the ownership gate that already guards a person's own settings
then guards the credentials that speak for them. A base with a parent inherits its guard and gate; a
base without one carries `opts.guard`.

`CreateAccessToken.expiresIn` is **seconds** (the server clamps it to `AUTH_TOKEN_MAX_TTL`), while a
UI usually offers days — convert at the form, and omit the field entirely for "never" rather than
sending a zero.

## Depends On

- `@owlmeans/auth` — `AuthRole`, the `Authorization` header name
- `@owlmeans/context`, `@owlmeans/entrypoint`, `@owlmeans/resource`, `@owlmeans/route`

## Related

- [[server-auth-token]] — the store, the verifying guard (audience admission), issuance, the handlers and the coguard
- [[oauth]] — the OAuth vocabulary that mints audience-scoped tokens
- [[web-auth-token]] — the management panel and its hook
- [[auth-protocol]] — where long-lived tokens sit among the other authentication paths
- [[auth-common]] — `authMiddleware`, `DEFAULT_GUARD`, `extractAuthToken`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…