@hyper.authio/server 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -2
- package/dist/index.cjs +739 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +439 -0
- package/dist/index.d.ts +439 -0
- package/dist/index.js +714 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -4
package/README.md
CHANGED
|
@@ -1,3 +1,115 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hyper.authio/server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Protect a Node.js API with Authio, in any framework: token validation, the caller with every form of permission
|
|
4
|
+
Authio issues, authorization rules, a webhook receiver, and calls to downstream APIs. For Express, use
|
|
5
|
+
[`@hyper.authio/express`](../express/README.md), built on this package.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @hyper.authio/server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Validate tokens
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createAuthioVerifier, AuthioBearerError } from "@hyper.authio/server";
|
|
15
|
+
|
|
16
|
+
const verifier = createAuthioVerifier({
|
|
17
|
+
issuer: "https://auth.example.com/realms/acme",
|
|
18
|
+
audiences: "https://orders.example.com", // the API's resource identifier (RFC 8707)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const user = await verifier.verifyAuthorizationHeader(request.headers.authorization);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error instanceof AuthioBearerError) {
|
|
25
|
+
response.setHeader("WWW-Authenticate", error.wwwAuthenticate);
|
|
26
|
+
response.statusCode = 401;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Option | Default | |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `audiences` | | Required. |
|
|
34
|
+
| `introspection` | `none` | `none`: local JWT validation. `activeCheck`: then confirmed active by introspection (cached per `jti`), so a revoked token, an ended session or a suspension is refused before the token expires. `introspectionOnly`: introspection alone, for opaque or encrypted tokens. |
|
|
35
|
+
| `client` | | The API's confidential client, needed by introspection: an `AuthioClient` or `{ clientId, clientSecret }` (or a private key). It must be the resource server's introspection client. |
|
|
36
|
+
| `introspectionCacheDuration` | 30 s | Never past the token's expiry; only active answers are kept. |
|
|
37
|
+
| `clockSkew` | 30 s | |
|
|
38
|
+
| `validAlgorithms` | RS*, PS*, ES* | |
|
|
39
|
+
| `rejectNonAccessTokens` | `true` | Refuses a token whose `typ` is not `Bearer` (an ID token, for one). |
|
|
40
|
+
| `roleClaims` | `["role"]` | Claims holding flat role names (a Role claim mapper). |
|
|
41
|
+
| `groupClaims` | `["groups"]` | Claims holding group names (a Group claim mapper). |
|
|
42
|
+
|
|
43
|
+
A refusal is an `AuthioBearerError` whose `wwwAuthenticate` is `Bearer` when the request had no token, and
|
|
44
|
+
`Bearer error="invalid_token", error_description="…"` otherwise. A token addressed elsewhere is described with the
|
|
45
|
+
audience the API accepts; introspection failures fail closed.
|
|
46
|
+
|
|
47
|
+
## The caller
|
|
48
|
+
|
|
49
|
+
`AuthioUser` reads every form of permission, the same way from a JWT or an introspection answer:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
user.subject; user.clientId; user.scopes;
|
|
53
|
+
user.roles; // *_resource_access roles, then flat role claims
|
|
54
|
+
user.permissions; // { orders: ["read", "write"] }
|
|
55
|
+
user.resourceAccess; // per-source entries: user, realm, client, plan, consumer, secret, system
|
|
56
|
+
user.groups; // from the group claims
|
|
57
|
+
user.organizationId; user.organizationAlias; user.authenticationLevel; user.authenticationMethods; user.actor;
|
|
58
|
+
user.claims; // every claim, for claim-mapper claims and the person's own claims
|
|
59
|
+
|
|
60
|
+
user.hasPermission("orders", "read");
|
|
61
|
+
user.hasPermission("orders", "read", { sources: ["realm", "client"] });
|
|
62
|
+
user.hasRole("admin", { sources: ["user"] });
|
|
63
|
+
user.hasGroup("finance");
|
|
64
|
+
user.hasClaim("department", "finance", "sales");
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The raw token is kept as `user.token`, not enumerable, so it stays out of logs and JSON.
|
|
68
|
+
|
|
69
|
+
## Authorize
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { authorize, requirePermission, requireGroup, requireAuthenticationLevel, allOf, anyOf, AuthioForbiddenError } from "@hyper.authio/server";
|
|
73
|
+
|
|
74
|
+
const refusal = authorize(user, requirePermission("orders", "write"), anyOf(requireGroup("finance"), requireAuthenticationLevel(2)));
|
|
75
|
+
|
|
76
|
+
if (refusal) {
|
|
77
|
+
const error = new AuthioForbiddenError(refusal);
|
|
78
|
+
// 403, with error.wwwAuthenticate when the refusal is about scopes (insufficient_scope) or the sign-in
|
|
79
|
+
// (insufficient_user_authentication); toProblemDetails(error) for an RFC 9457 body.
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Rules: `requireScope`, `requireRole`, `requirePermission`, `requireGroup`, `requireClaim`, `requireOrganization`,
|
|
84
|
+
`requireAuthenticationLevel`, `requireAuthenticationMethod`, `requireClient`, combined with `allOf` and `anyOf`.
|
|
85
|
+
`authorize` checks every rule and joins their messages.
|
|
86
|
+
|
|
87
|
+
## Webhooks
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { createAuthioWebhooks } from "@hyper.authio/server";
|
|
91
|
+
|
|
92
|
+
const webhooks = createAuthioWebhooks({ secret: process.env.AUTHIO_WEBHOOK_SECRET })
|
|
93
|
+
.on("user_created", async (event) => { /* event.data.username */ })
|
|
94
|
+
.on("*", (event) => log(event.type));
|
|
95
|
+
|
|
96
|
+
const outcome = await webhooks.handle({ body: rawBody, headers: request.headers });
|
|
97
|
+
response.statusCode = outcome.status; // 200 handled or duplicate, 400, 401, 413, 500
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Deliveries are deduplicated by `X-Authio-Event-Id` for 24 hours in memory (or a store you pass); a handler that
|
|
101
|
+
throws releases the id so the redelivery runs. Several `secrets` are accepted while one is rotated.
|
|
102
|
+
|
|
103
|
+
## Downstream APIs
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { createForwardingFetch, createOnBehalfOf } from "@hyper.authio/server";
|
|
107
|
+
|
|
108
|
+
const sameAudience = createForwardingFetch(user); // forwards the caller's own token
|
|
109
|
+
const billing = createOnBehalfOf({ client: authioClient, resource: "https://billing.example.com", scopes: "invoices:read" });
|
|
110
|
+
await billing.fetch(user)("https://billing.example.com/invoices"); // exchanged once, cached, retried once on 401
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|