@hyper.authio/client 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 CHANGED
@@ -1,3 +1,121 @@
1
- # Temporary Holding Version
1
+ # @hyper.authio/client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Authio client for Node.js 20.19+ and edge runtimes: discovery, every grant, client authentication with a secret
4
+ or `private_key_jwt`, introspection, revocation, UserInfo, a cached client-credentials `fetch`, the roles and
5
+ permissions of a token, and webhook verification. Its only dependency is [`jose`](https://github.com/panva/jose).
6
+
7
+ ```bash
8
+ npm install @hyper.authio/client
9
+ ```
10
+
11
+ ## Configure
12
+
13
+ ```ts
14
+ import { createAuthioClient } from "@hyper.authio/client";
15
+
16
+ const authio = createAuthioClient({
17
+ issuer: "https://auth.example.com/realms/acme", // or authority + realm
18
+ clientId: "orders-worker",
19
+ clientSecret: process.env.AUTHIO_CLIENT_SECRET,
20
+ });
21
+ ```
22
+
23
+ | Option | Default | |
24
+ | --- | --- | --- |
25
+ | `issuer`, or `authority` + `realm` | | The realm's issuer. Must be HTTPS, except on loopback or with `requireHttps: false`. |
26
+ | `clientId` | | Required. |
27
+ | `clientAuthentication` | `private_key_jwt` with only a key, else `client_secret_basic` | Also `client_secret_post` and `none` (a public client). |
28
+ | `clientSecret` | | For `client_secret_basic` / `client_secret_post`. |
29
+ | `clientPrivateKey`, `clientPrivateKeyPath`, `clientKeyId`, `clientSigningAlgorithm` | RS256 for RSA, the curve's ES algorithm for EC | For `private_key_jwt`: PEM (PKCS#8, PKCS#1, SEC1) or JWK; the file is re-read when it changes; RSA keys need 2048 bits. |
30
+ | `clientAssertionLifetime` | 120 s | At most 3600. |
31
+ | `clientEncryptionKey` | | The client's own key, to read a UserInfo Authio answers encrypted. |
32
+ | `discoveryCacheDuration` | 3600 s | |
33
+ | `httpTimeout` | 30 s | |
34
+ | `tokenRefreshSkew` | 60 s | How early cached tokens are renewed (at most half their lifetime). |
35
+ | `fetch`, `clock` | global `fetch`, system clock | Replace them in tests. |
36
+
37
+ Every endpoint is read from the discovery document, which is cached, read once for concurrent callers, and refused
38
+ when it names another issuer.
39
+
40
+ ## Grants
41
+
42
+ ```ts
43
+ await authio.tokens.clientCredentials({ scopes: ["orders:read"], resource: "https://orders.example.com" });
44
+ await authio.tokens.authorizationCode({ code, redirectUri, codeVerifier });
45
+ await authio.tokens.refresh({ refreshToken }); // store the rotated refresh_token it returns
46
+ await authio.tokens.exchange({ subjectToken, resource }); // on-behalf-of (RFC 8693)
47
+ await authio.tokens.password({ username, password });
48
+
49
+ const device = await authio.tokens.deviceAuthorization({ scopes: "openid" });
50
+ console.log(device.verification_uri_complete);
51
+ const token = await authio.tokens.pollDevice(device); // waits, slows down, throws access_denied / expired_token
52
+
53
+ const connection = await authio.tokens.exchangeForConnection({ subjectToken, provider: "github" });
54
+ switch (connection.status) {
55
+ case "granted": /* connection.accessToken */ break;
56
+ case "required": /* send the person to connection.connectUri */ break;
57
+ case "selection_required": /* ask again with connectionId */ break;
58
+ case "refused": /* connection.error */ break;
59
+ }
60
+ ```
61
+
62
+ OAuth errors throw `AuthioProtocolError` (`error`, `errorDescription`, `errorCode`, `status`); `error` is
63
+ `http_{status}` when Authio sent none, `invalid_response` when a success body could not be read. Tokens print as
64
+ `[redacted]` in `console.log` and `util.inspect`, and serialize normally.
65
+
66
+ ## Machine-to-machine calls
67
+
68
+ ```ts
69
+ const ordersFetch = authio.clientCredentialsFetch({ scopes: "orders:read", resource: "https://orders.example.com" });
70
+ await ordersFetch("https://orders.example.com/orders");
71
+ ```
72
+
73
+ The token is cached per scopes and resource, renewed before expiry with one request for concurrent callers, and on a
74
+ `401` dropped and retried once. `authio.clientCredentials.getToken()` gives the token itself.
75
+
76
+ ## Introspection, revocation, UserInfo
77
+
78
+ ```ts
79
+ const result = await authio.introspect(token); // { active, sub, scope, …, and Authio's extra members }
80
+ await authio.revoke(refreshToken, { tokenTypeHint: "refresh_token" });
81
+ const info = await authio.userInfo(accessToken); // JSON, or a signed / encrypted JWT verified for you
82
+ ```
83
+
84
+ Authio answers introspection according to who asks: a token requested for a resource is active only to the client
85
+ that resource server names as its introspection client.
86
+
87
+ ## Roles and permissions
88
+
89
+ ```ts
90
+ import { parseResourceAccess } from "@hyper.authio/client";
91
+
92
+ const access = parseResourceAccess(jwtPayload); // or introspection answer, or [type, value] pairs
93
+ access.roles; // ["billing-admin", "viewer"]
94
+ access.permissions; // { invoice: ["read", "write"], report: ["read"] }
95
+ access.grants("invoice", "write"); // true, case-insensitive
96
+ access.grants("report", "read", ["user"]); // only roles from user_resource_access
97
+ access.fromSource("client"); // the client's roles
98
+ ```
99
+
100
+ The seven `*_resource_access` claims (`user`, `realm`, `client`, `plan`, `consumer`, `secret`, `system`) are merged.
101
+ A claim that is not exactly `{ role: { resource: [method] } }` contributes nothing, as in Authio's own check. To
102
+ protect an API, use [`@hyper.authio/server`](../server/README.md), which also reads groups, mapper claims and the rest.
103
+
104
+ ## Webhooks
105
+
106
+ ```ts
107
+ import { verifyWebhookSignature, parseWebhookEvent, asUserEvent } from "@hyper.authio/client";
108
+
109
+ const result = await verifyWebhookSignature(request.headers["x-authio-signature"], rawBody, secret);
110
+ if (!result.valid) return reply(401);
111
+
112
+ const event = parseWebhookEvent(rawBody);
113
+ const user = asUserEvent(event); // typed data for user_created / user_updated / user_deleted, else undefined
114
+ ```
115
+
116
+ The signature is HMAC-SHA256 of `{t}.{body}`, compared in constant time; the timestamp must be within 5 minutes.
117
+ [`@hyper.authio/server`](../server/README.md) adds deduplication and dispatch.
118
+
119
+ ## License
120
+
121
+ MIT