@keycardai/oauth 0.5.0 → 0.6.0
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 +45 -0
- package/dist/cjs/pkce.d.ts +64 -0
- package/dist/cjs/pkce.d.ts.map +1 -0
- package/dist/cjs/pkce.js +249 -0
- package/dist/cjs/pkce.js.map +1 -0
- package/dist/esm/pkce.d.ts +64 -0
- package/dist/esm/pkce.d.ts.map +1 -0
- package/dist/esm/pkce.js +206 -0
- package/dist/esm/pkce.js.map +1 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -157,6 +157,40 @@ returns the issued client credentials. Throws `OAuthError` on RFC 6749 §5.2
|
|
|
157
157
|
error responses, a plain `Error` on missing `registration_endpoint` or
|
|
158
158
|
non-OAuth HTTP failures.
|
|
159
159
|
|
|
160
|
+
### PKCE (RFC 7636)
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import {
|
|
164
|
+
generateCodeVerifier,
|
|
165
|
+
generateCodeChallenge,
|
|
166
|
+
generatePkcePair,
|
|
167
|
+
exchangeAuthorizationCode,
|
|
168
|
+
authenticate,
|
|
169
|
+
} from "@keycardai/oauth/pkce";
|
|
170
|
+
|
|
171
|
+
// Generate primitives for a custom auth-code flow
|
|
172
|
+
const { codeVerifier, codeChallenge } = await generatePkcePair();
|
|
173
|
+
|
|
174
|
+
// Exchange code received at the redirect URI
|
|
175
|
+
const tokens = await exchangeAuthorizationCode(
|
|
176
|
+
"https://your-zone.keycard.cloud",
|
|
177
|
+
authorizationCode,
|
|
178
|
+
{ codeVerifier, redirectUri: "https://app.example.com/callback", clientId: "my-client" },
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Or let authenticate() drive the full flow (Node.js only)
|
|
182
|
+
const tokens2 = await authenticate("https://your-zone.keycard.cloud", {
|
|
183
|
+
clientId: "my-client",
|
|
184
|
+
scopes: ["read", "write"],
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`generateCodeVerifier` and `generateCodeChallenge` use the global `crypto` API and
|
|
189
|
+
are runtime-agnostic (Node.js, Cloudflare Workers, browser). `authenticate()` drives
|
|
190
|
+
the full browser-launch and loopback-callback flow and **requires Node.js** — it uses
|
|
191
|
+
dynamic imports of `node:http` and `node:child_process` and will throw a runtime error
|
|
192
|
+
if called in a non-Node environment.
|
|
193
|
+
|
|
160
194
|
## API Overview
|
|
161
195
|
|
|
162
196
|
### JWKS Key Management
|
|
@@ -185,6 +219,17 @@ non-OAuth HTTP failures.
|
|
|
185
219
|
| `buildSubstituteUserToken` | `@keycardai/oauth/jwt/substituteUser` | Builds the unsigned subject JWT for impersonation calls |
|
|
186
220
|
| `registerClient` | `@keycardai/oauth/registration` | RFC 7591 dynamic client registration with auto-discovery |
|
|
187
221
|
|
|
222
|
+
### PKCE (RFC 7636)
|
|
223
|
+
|
|
224
|
+
| Export | Import Path | Description |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `generateCodeVerifier` | `@keycardai/oauth/pkce` | Generates a 43-char random code verifier (RFC 7636 §4.1) |
|
|
227
|
+
| `generateCodeChallenge` | `@keycardai/oauth/pkce` | Computes S256 or plain code challenge from a verifier (RFC 7636 §4.2) |
|
|
228
|
+
| `generatePkcePair` | `@keycardai/oauth/pkce` | Convenience: generates verifier + challenge in one call |
|
|
229
|
+
| `exchangeAuthorizationCode` | `@keycardai/oauth/pkce` | Exchanges an authorization code with code_verifier at the token endpoint |
|
|
230
|
+
| `authenticate` | `@keycardai/oauth/pkce` | Full browser-launch and loopback-callback flow. **Node.js only** |
|
|
231
|
+
| `Pkce` (type) | `@keycardai/oauth/pkce` | `{ codeVerifier, codeChallenge, codeChallengeMethod }` |
|
|
232
|
+
|
|
188
233
|
### Server-tier Primitives
|
|
189
234
|
|
|
190
235
|
| Export | Import Path | Description |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { TokenResponse } from "./tokenExchange.js";
|
|
2
|
+
export interface Pkce {
|
|
3
|
+
codeVerifier: string;
|
|
4
|
+
codeChallenge: string;
|
|
5
|
+
codeChallengeMethod: "S256" | "plain";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generate a cryptographically random PKCE code verifier (RFC 7636 §4.1).
|
|
9
|
+
*
|
|
10
|
+
* Returns a 43-character base64url string (32 random bytes). Runtime-agnostic:
|
|
11
|
+
* uses the global `crypto.getRandomValues` which is available in Node 19+,
|
|
12
|
+
* Cloudflare Workers, and browsers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateCodeVerifier(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Derive a PKCE code challenge from a code verifier (RFC 7636 §4.2).
|
|
17
|
+
*
|
|
18
|
+
* S256 (default): `BASE64URL(SHA-256(ASCII(code_verifier)))`
|
|
19
|
+
* plain: returns the verifier unchanged (not recommended; use only when
|
|
20
|
+
* the AS does not support S256).
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateCodeChallenge(verifier: string, method?: "S256" | "plain"): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Generate a PKCE pair (verifier + challenge) in one call.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generatePkcePair(method?: "S256" | "plain"): Promise<Pkce>;
|
|
27
|
+
export interface ExchangeAuthorizationCodeOptions {
|
|
28
|
+
codeVerifier: string;
|
|
29
|
+
redirectUri: string;
|
|
30
|
+
clientId?: string;
|
|
31
|
+
clientSecret?: string;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Exchange an authorization code for tokens (RFC 6749 §4.1.3 + RFC 7636).
|
|
36
|
+
*
|
|
37
|
+
* Discovers `token_endpoint` from the AS metadata, then POSTs
|
|
38
|
+
* `grant_type=authorization_code` with the code verifier.
|
|
39
|
+
*/
|
|
40
|
+
export declare function exchangeAuthorizationCode(issuerUrl: string, code: string, options: ExchangeAuthorizationCodeOptions): Promise<TokenResponse>;
|
|
41
|
+
export interface AuthenticateOptions {
|
|
42
|
+
clientId: string;
|
|
43
|
+
/** Default: "http://localhost:{port}/callback" */
|
|
44
|
+
redirectUri?: string;
|
|
45
|
+
/** Default: 8080 */
|
|
46
|
+
port?: number;
|
|
47
|
+
scopes?: readonly string[];
|
|
48
|
+
clientSecret?: string;
|
|
49
|
+
/** Default: 60_000 ms */
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Full authorization-code-with-PKCE flow for local/CLI contexts.
|
|
54
|
+
*
|
|
55
|
+
* Generates a PKCE pair, builds the authorization URL, opens the user's
|
|
56
|
+
* browser, starts a local loopback HTTP server to receive the redirect,
|
|
57
|
+
* and exchanges the authorization code for tokens.
|
|
58
|
+
*
|
|
59
|
+
* **Requires Node.js.** Uses `node:http` and `node:child_process` via
|
|
60
|
+
* dynamic import. Importing this module is safe in any runtime; only
|
|
61
|
+
* *calling* `authenticate()` requires Node.js.
|
|
62
|
+
*/
|
|
63
|
+
export declare function authenticate(issuerUrl: string, options: AuthenticateOptions): Promise<TokenResponse>;
|
|
64
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/pkce.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,MAAM,WAAW,IAAI;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAM,GAAG,OAAgB,GAChC,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,MAAM,GAAG,OAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF;AAMD,MAAM,WAAW,gCAAgC;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,aAAa,CAAC,CAyExB;AAMD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAkCxB"}
|
package/dist/cjs/pkce.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.generateCodeVerifier = generateCodeVerifier;
|
|
40
|
+
exports.generateCodeChallenge = generateCodeChallenge;
|
|
41
|
+
exports.generatePkcePair = generatePkcePair;
|
|
42
|
+
exports.exchangeAuthorizationCode = exchangeAuthorizationCode;
|
|
43
|
+
exports.authenticate = authenticate;
|
|
44
|
+
const base64url_js_1 = __importDefault(require("./base64url.js"));
|
|
45
|
+
const discovery_js_1 = require("./discovery.js");
|
|
46
|
+
const errors_js_1 = require("./errors.js");
|
|
47
|
+
/**
|
|
48
|
+
* Generate a cryptographically random PKCE code verifier (RFC 7636 §4.1).
|
|
49
|
+
*
|
|
50
|
+
* Returns a 43-character base64url string (32 random bytes). Runtime-agnostic:
|
|
51
|
+
* uses the global `crypto.getRandomValues` which is available in Node 19+,
|
|
52
|
+
* Cloudflare Workers, and browsers.
|
|
53
|
+
*/
|
|
54
|
+
function generateCodeVerifier() {
|
|
55
|
+
const bytes = new Uint8Array(32);
|
|
56
|
+
crypto.getRandomValues(bytes);
|
|
57
|
+
return base64url_js_1.default.encode(bytes.buffer);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Derive a PKCE code challenge from a code verifier (RFC 7636 §4.2).
|
|
61
|
+
*
|
|
62
|
+
* S256 (default): `BASE64URL(SHA-256(ASCII(code_verifier)))`
|
|
63
|
+
* plain: returns the verifier unchanged (not recommended; use only when
|
|
64
|
+
* the AS does not support S256).
|
|
65
|
+
*/
|
|
66
|
+
async function generateCodeChallenge(verifier, method = "S256") {
|
|
67
|
+
if (method === "plain") {
|
|
68
|
+
return verifier;
|
|
69
|
+
}
|
|
70
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
71
|
+
return base64url_js_1.default.encode(digest);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate a PKCE pair (verifier + challenge) in one call.
|
|
75
|
+
*/
|
|
76
|
+
async function generatePkcePair(method = "S256") {
|
|
77
|
+
const codeVerifier = generateCodeVerifier();
|
|
78
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier, method);
|
|
79
|
+
return { codeVerifier, codeChallenge, codeChallengeMethod: method };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Exchange an authorization code for tokens (RFC 6749 §4.1.3 + RFC 7636).
|
|
83
|
+
*
|
|
84
|
+
* Discovers `token_endpoint` from the AS metadata, then POSTs
|
|
85
|
+
* `grant_type=authorization_code` with the code verifier.
|
|
86
|
+
*/
|
|
87
|
+
async function exchangeAuthorizationCode(issuerUrl, code, options) {
|
|
88
|
+
const metadata = await (0, discovery_js_1.fetchAuthorizationServerMetadata)(issuerUrl, {
|
|
89
|
+
signal: options.signal,
|
|
90
|
+
});
|
|
91
|
+
if (!metadata.token_endpoint) {
|
|
92
|
+
throw new Error(`Authorization server "${issuerUrl}" does not advertise a token_endpoint`);
|
|
93
|
+
}
|
|
94
|
+
const params = new URLSearchParams();
|
|
95
|
+
params.set("grant_type", "authorization_code");
|
|
96
|
+
params.set("code", code);
|
|
97
|
+
params.set("code_verifier", options.codeVerifier);
|
|
98
|
+
params.set("redirect_uri", options.redirectUri);
|
|
99
|
+
if (options.clientId)
|
|
100
|
+
params.set("client_id", options.clientId);
|
|
101
|
+
const headers = {
|
|
102
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
103
|
+
};
|
|
104
|
+
if (options.clientId && options.clientSecret) {
|
|
105
|
+
headers["Authorization"] = `Basic ${btoa(`${options.clientId}:${options.clientSecret}`)}`;
|
|
106
|
+
params.delete("client_id");
|
|
107
|
+
}
|
|
108
|
+
const response = await fetch(metadata.token_endpoint, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers,
|
|
111
|
+
body: params.toString(),
|
|
112
|
+
signal: options.signal,
|
|
113
|
+
});
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
let errorBody = null;
|
|
116
|
+
try {
|
|
117
|
+
const json = await response.json();
|
|
118
|
+
if (json && typeof json === "object" && !Array.isArray(json)) {
|
|
119
|
+
errorBody = json;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// non-JSON error body — fall through to generic error
|
|
124
|
+
}
|
|
125
|
+
if (errorBody && typeof errorBody.error === "string") {
|
|
126
|
+
const description = typeof errorBody.error_description === "string"
|
|
127
|
+
? errorBody.error_description
|
|
128
|
+
: errorBody.error;
|
|
129
|
+
const errorUri = typeof errorBody.error_uri === "string" ? errorBody.error_uri : undefined;
|
|
130
|
+
throw new errors_js_1.OAuthError(errorBody.error, description, errorUri);
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`Authorization code exchange failed (HTTP ${response.status})`);
|
|
133
|
+
}
|
|
134
|
+
const json = await response.json();
|
|
135
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
136
|
+
throw new Error("Token endpoint response is not a valid JSON object");
|
|
137
|
+
}
|
|
138
|
+
const body = json;
|
|
139
|
+
const accessToken = body.access_token;
|
|
140
|
+
if (typeof accessToken !== "string" || !accessToken) {
|
|
141
|
+
throw new Error("Token endpoint response missing access_token");
|
|
142
|
+
}
|
|
143
|
+
const tokenResponse = {
|
|
144
|
+
accessToken,
|
|
145
|
+
tokenType: typeof body.token_type === "string" ? body.token_type : "bearer",
|
|
146
|
+
};
|
|
147
|
+
if (typeof body.expires_in === "number")
|
|
148
|
+
tokenResponse.expiresIn = body.expires_in;
|
|
149
|
+
if (typeof body.refresh_token === "string")
|
|
150
|
+
tokenResponse.refreshToken = body.refresh_token;
|
|
151
|
+
if (typeof body.scope === "string") {
|
|
152
|
+
tokenResponse.scope = body.scope.split(" ").filter(Boolean);
|
|
153
|
+
}
|
|
154
|
+
return tokenResponse;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Full authorization-code-with-PKCE flow for local/CLI contexts.
|
|
158
|
+
*
|
|
159
|
+
* Generates a PKCE pair, builds the authorization URL, opens the user's
|
|
160
|
+
* browser, starts a local loopback HTTP server to receive the redirect,
|
|
161
|
+
* and exchanges the authorization code for tokens.
|
|
162
|
+
*
|
|
163
|
+
* **Requires Node.js.** Uses `node:http` and `node:child_process` via
|
|
164
|
+
* dynamic import. Importing this module is safe in any runtime; only
|
|
165
|
+
* *calling* `authenticate()` requires Node.js.
|
|
166
|
+
*/
|
|
167
|
+
async function authenticate(issuerUrl, options) {
|
|
168
|
+
const port = options.port ?? 8080;
|
|
169
|
+
const redirectUri = options.redirectUri ?? `http://localhost:${port}/callback`;
|
|
170
|
+
const timeoutMs = options.timeoutMs ?? 60_000;
|
|
171
|
+
const { codeVerifier, codeChallenge } = await generatePkcePair("S256");
|
|
172
|
+
const metadata = await (0, discovery_js_1.fetchAuthorizationServerMetadata)(issuerUrl);
|
|
173
|
+
if (!metadata.authorization_endpoint) {
|
|
174
|
+
throw new Error(`Authorization server "${issuerUrl}" does not advertise an authorization_endpoint`);
|
|
175
|
+
}
|
|
176
|
+
const authUrl = new URL(metadata.authorization_endpoint);
|
|
177
|
+
authUrl.searchParams.set("response_type", "code");
|
|
178
|
+
authUrl.searchParams.set("client_id", options.clientId);
|
|
179
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
180
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
181
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
182
|
+
if (options.scopes && options.scopes.length > 0) {
|
|
183
|
+
authUrl.searchParams.set("scope", options.scopes.join(" "));
|
|
184
|
+
}
|
|
185
|
+
await openBrowser(authUrl.toString());
|
|
186
|
+
const code = await waitForCode(port, redirectUri, timeoutMs);
|
|
187
|
+
return exchangeAuthorizationCode(issuerUrl, code, {
|
|
188
|
+
codeVerifier,
|
|
189
|
+
redirectUri,
|
|
190
|
+
clientId: options.clientId,
|
|
191
|
+
clientSecret: options.clientSecret,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function openBrowser(url) {
|
|
195
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require("node:child_process")));
|
|
196
|
+
if (process.platform === "darwin") {
|
|
197
|
+
execFile("open", [url]);
|
|
198
|
+
}
|
|
199
|
+
else if (process.platform === "win32") {
|
|
200
|
+
// `start` is a cmd.exe built-in, not a standalone executable.
|
|
201
|
+
execFile("cmd", ["/c", "start", "", url]);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
execFile("xdg-open", [url]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function waitForCode(port, redirectUri, timeoutMs) {
|
|
208
|
+
// Import before entering the Promise constructor to avoid the async-executor
|
|
209
|
+
// anti-pattern: if the dynamic import throws, the rejection propagates through
|
|
210
|
+
// this async function rather than escaping an async Promise constructor.
|
|
211
|
+
const { createServer } = await Promise.resolve().then(() => __importStar(require("node:http")));
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const timer = setTimeout(() => {
|
|
214
|
+
server.close();
|
|
215
|
+
reject(new Error(`PKCE authentication timed out after ${timeoutMs}ms`));
|
|
216
|
+
}, timeoutMs);
|
|
217
|
+
const server = createServer((req, res) => {
|
|
218
|
+
try {
|
|
219
|
+
const reqUrl = new URL(req.url ?? "/", redirectUri);
|
|
220
|
+
const code = reqUrl.searchParams.get("code");
|
|
221
|
+
const error = reqUrl.searchParams.get("error");
|
|
222
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
223
|
+
res.end("<html><body><p>Authentication complete. You can close this tab.</p></body></html>");
|
|
224
|
+
server.close();
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
if (error) {
|
|
227
|
+
reject(new errors_js_1.OAuthError(error, reqUrl.searchParams.get("error_description") ?? error));
|
|
228
|
+
}
|
|
229
|
+
else if (code) {
|
|
230
|
+
resolve(code);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
reject(new Error("No authorization code in redirect"));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
server.close();
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
reject(e);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
server.listen(port, "localhost");
|
|
243
|
+
server.on("error", (err) => {
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
reject(new Error(`Failed to start loopback server on port ${port}: ${err.message}`));
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/pkce.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { TokenResponse } from "./tokenExchange.js";
|
|
2
|
+
export interface Pkce {
|
|
3
|
+
codeVerifier: string;
|
|
4
|
+
codeChallenge: string;
|
|
5
|
+
codeChallengeMethod: "S256" | "plain";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generate a cryptographically random PKCE code verifier (RFC 7636 §4.1).
|
|
9
|
+
*
|
|
10
|
+
* Returns a 43-character base64url string (32 random bytes). Runtime-agnostic:
|
|
11
|
+
* uses the global `crypto.getRandomValues` which is available in Node 19+,
|
|
12
|
+
* Cloudflare Workers, and browsers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateCodeVerifier(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Derive a PKCE code challenge from a code verifier (RFC 7636 §4.2).
|
|
17
|
+
*
|
|
18
|
+
* S256 (default): `BASE64URL(SHA-256(ASCII(code_verifier)))`
|
|
19
|
+
* plain: returns the verifier unchanged (not recommended; use only when
|
|
20
|
+
* the AS does not support S256).
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateCodeChallenge(verifier: string, method?: "S256" | "plain"): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Generate a PKCE pair (verifier + challenge) in one call.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generatePkcePair(method?: "S256" | "plain"): Promise<Pkce>;
|
|
27
|
+
export interface ExchangeAuthorizationCodeOptions {
|
|
28
|
+
codeVerifier: string;
|
|
29
|
+
redirectUri: string;
|
|
30
|
+
clientId?: string;
|
|
31
|
+
clientSecret?: string;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Exchange an authorization code for tokens (RFC 6749 §4.1.3 + RFC 7636).
|
|
36
|
+
*
|
|
37
|
+
* Discovers `token_endpoint` from the AS metadata, then POSTs
|
|
38
|
+
* `grant_type=authorization_code` with the code verifier.
|
|
39
|
+
*/
|
|
40
|
+
export declare function exchangeAuthorizationCode(issuerUrl: string, code: string, options: ExchangeAuthorizationCodeOptions): Promise<TokenResponse>;
|
|
41
|
+
export interface AuthenticateOptions {
|
|
42
|
+
clientId: string;
|
|
43
|
+
/** Default: "http://localhost:{port}/callback" */
|
|
44
|
+
redirectUri?: string;
|
|
45
|
+
/** Default: 8080 */
|
|
46
|
+
port?: number;
|
|
47
|
+
scopes?: readonly string[];
|
|
48
|
+
clientSecret?: string;
|
|
49
|
+
/** Default: 60_000 ms */
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Full authorization-code-with-PKCE flow for local/CLI contexts.
|
|
54
|
+
*
|
|
55
|
+
* Generates a PKCE pair, builds the authorization URL, opens the user's
|
|
56
|
+
* browser, starts a local loopback HTTP server to receive the redirect,
|
|
57
|
+
* and exchanges the authorization code for tokens.
|
|
58
|
+
*
|
|
59
|
+
* **Requires Node.js.** Uses `node:http` and `node:child_process` via
|
|
60
|
+
* dynamic import. Importing this module is safe in any runtime; only
|
|
61
|
+
* *calling* `authenticate()` requires Node.js.
|
|
62
|
+
*/
|
|
63
|
+
export declare function authenticate(issuerUrl: string, options: AuthenticateOptions): Promise<TokenResponse>;
|
|
64
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/pkce.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,MAAM,WAAW,IAAI;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAM,GAAG,OAAgB,GAChC,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,MAAM,GAAG,OAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF;AAMD,MAAM,WAAW,gCAAgC;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,aAAa,CAAC,CAyExB;AAMD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAkCxB"}
|
package/dist/esm/pkce.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import base64url from "./base64url.js";
|
|
2
|
+
import { fetchAuthorizationServerMetadata } from "./discovery.js";
|
|
3
|
+
import { OAuthError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Generate a cryptographically random PKCE code verifier (RFC 7636 §4.1).
|
|
6
|
+
*
|
|
7
|
+
* Returns a 43-character base64url string (32 random bytes). Runtime-agnostic:
|
|
8
|
+
* uses the global `crypto.getRandomValues` which is available in Node 19+,
|
|
9
|
+
* Cloudflare Workers, and browsers.
|
|
10
|
+
*/
|
|
11
|
+
export function generateCodeVerifier() {
|
|
12
|
+
const bytes = new Uint8Array(32);
|
|
13
|
+
crypto.getRandomValues(bytes);
|
|
14
|
+
return base64url.encode(bytes.buffer);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Derive a PKCE code challenge from a code verifier (RFC 7636 §4.2).
|
|
18
|
+
*
|
|
19
|
+
* S256 (default): `BASE64URL(SHA-256(ASCII(code_verifier)))`
|
|
20
|
+
* plain: returns the verifier unchanged (not recommended; use only when
|
|
21
|
+
* the AS does not support S256).
|
|
22
|
+
*/
|
|
23
|
+
export async function generateCodeChallenge(verifier, method = "S256") {
|
|
24
|
+
if (method === "plain") {
|
|
25
|
+
return verifier;
|
|
26
|
+
}
|
|
27
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
28
|
+
return base64url.encode(digest);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate a PKCE pair (verifier + challenge) in one call.
|
|
32
|
+
*/
|
|
33
|
+
export async function generatePkcePair(method = "S256") {
|
|
34
|
+
const codeVerifier = generateCodeVerifier();
|
|
35
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier, method);
|
|
36
|
+
return { codeVerifier, codeChallenge, codeChallengeMethod: method };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Exchange an authorization code for tokens (RFC 6749 §4.1.3 + RFC 7636).
|
|
40
|
+
*
|
|
41
|
+
* Discovers `token_endpoint` from the AS metadata, then POSTs
|
|
42
|
+
* `grant_type=authorization_code` with the code verifier.
|
|
43
|
+
*/
|
|
44
|
+
export async function exchangeAuthorizationCode(issuerUrl, code, options) {
|
|
45
|
+
const metadata = await fetchAuthorizationServerMetadata(issuerUrl, {
|
|
46
|
+
signal: options.signal,
|
|
47
|
+
});
|
|
48
|
+
if (!metadata.token_endpoint) {
|
|
49
|
+
throw new Error(`Authorization server "${issuerUrl}" does not advertise a token_endpoint`);
|
|
50
|
+
}
|
|
51
|
+
const params = new URLSearchParams();
|
|
52
|
+
params.set("grant_type", "authorization_code");
|
|
53
|
+
params.set("code", code);
|
|
54
|
+
params.set("code_verifier", options.codeVerifier);
|
|
55
|
+
params.set("redirect_uri", options.redirectUri);
|
|
56
|
+
if (options.clientId)
|
|
57
|
+
params.set("client_id", options.clientId);
|
|
58
|
+
const headers = {
|
|
59
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
60
|
+
};
|
|
61
|
+
if (options.clientId && options.clientSecret) {
|
|
62
|
+
headers["Authorization"] = `Basic ${btoa(`${options.clientId}:${options.clientSecret}`)}`;
|
|
63
|
+
params.delete("client_id");
|
|
64
|
+
}
|
|
65
|
+
const response = await fetch(metadata.token_endpoint, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers,
|
|
68
|
+
body: params.toString(),
|
|
69
|
+
signal: options.signal,
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
let errorBody = null;
|
|
73
|
+
try {
|
|
74
|
+
const json = await response.json();
|
|
75
|
+
if (json && typeof json === "object" && !Array.isArray(json)) {
|
|
76
|
+
errorBody = json;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// non-JSON error body — fall through to generic error
|
|
81
|
+
}
|
|
82
|
+
if (errorBody && typeof errorBody.error === "string") {
|
|
83
|
+
const description = typeof errorBody.error_description === "string"
|
|
84
|
+
? errorBody.error_description
|
|
85
|
+
: errorBody.error;
|
|
86
|
+
const errorUri = typeof errorBody.error_uri === "string" ? errorBody.error_uri : undefined;
|
|
87
|
+
throw new OAuthError(errorBody.error, description, errorUri);
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Authorization code exchange failed (HTTP ${response.status})`);
|
|
90
|
+
}
|
|
91
|
+
const json = await response.json();
|
|
92
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
93
|
+
throw new Error("Token endpoint response is not a valid JSON object");
|
|
94
|
+
}
|
|
95
|
+
const body = json;
|
|
96
|
+
const accessToken = body.access_token;
|
|
97
|
+
if (typeof accessToken !== "string" || !accessToken) {
|
|
98
|
+
throw new Error("Token endpoint response missing access_token");
|
|
99
|
+
}
|
|
100
|
+
const tokenResponse = {
|
|
101
|
+
accessToken,
|
|
102
|
+
tokenType: typeof body.token_type === "string" ? body.token_type : "bearer",
|
|
103
|
+
};
|
|
104
|
+
if (typeof body.expires_in === "number")
|
|
105
|
+
tokenResponse.expiresIn = body.expires_in;
|
|
106
|
+
if (typeof body.refresh_token === "string")
|
|
107
|
+
tokenResponse.refreshToken = body.refresh_token;
|
|
108
|
+
if (typeof body.scope === "string") {
|
|
109
|
+
tokenResponse.scope = body.scope.split(" ").filter(Boolean);
|
|
110
|
+
}
|
|
111
|
+
return tokenResponse;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Full authorization-code-with-PKCE flow for local/CLI contexts.
|
|
115
|
+
*
|
|
116
|
+
* Generates a PKCE pair, builds the authorization URL, opens the user's
|
|
117
|
+
* browser, starts a local loopback HTTP server to receive the redirect,
|
|
118
|
+
* and exchanges the authorization code for tokens.
|
|
119
|
+
*
|
|
120
|
+
* **Requires Node.js.** Uses `node:http` and `node:child_process` via
|
|
121
|
+
* dynamic import. Importing this module is safe in any runtime; only
|
|
122
|
+
* *calling* `authenticate()` requires Node.js.
|
|
123
|
+
*/
|
|
124
|
+
export async function authenticate(issuerUrl, options) {
|
|
125
|
+
const port = options.port ?? 8080;
|
|
126
|
+
const redirectUri = options.redirectUri ?? `http://localhost:${port}/callback`;
|
|
127
|
+
const timeoutMs = options.timeoutMs ?? 60_000;
|
|
128
|
+
const { codeVerifier, codeChallenge } = await generatePkcePair("S256");
|
|
129
|
+
const metadata = await fetchAuthorizationServerMetadata(issuerUrl);
|
|
130
|
+
if (!metadata.authorization_endpoint) {
|
|
131
|
+
throw new Error(`Authorization server "${issuerUrl}" does not advertise an authorization_endpoint`);
|
|
132
|
+
}
|
|
133
|
+
const authUrl = new URL(metadata.authorization_endpoint);
|
|
134
|
+
authUrl.searchParams.set("response_type", "code");
|
|
135
|
+
authUrl.searchParams.set("client_id", options.clientId);
|
|
136
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
137
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
138
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
139
|
+
if (options.scopes && options.scopes.length > 0) {
|
|
140
|
+
authUrl.searchParams.set("scope", options.scopes.join(" "));
|
|
141
|
+
}
|
|
142
|
+
await openBrowser(authUrl.toString());
|
|
143
|
+
const code = await waitForCode(port, redirectUri, timeoutMs);
|
|
144
|
+
return exchangeAuthorizationCode(issuerUrl, code, {
|
|
145
|
+
codeVerifier,
|
|
146
|
+
redirectUri,
|
|
147
|
+
clientId: options.clientId,
|
|
148
|
+
clientSecret: options.clientSecret,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function openBrowser(url) {
|
|
152
|
+
const { execFile } = await import("node:child_process");
|
|
153
|
+
if (process.platform === "darwin") {
|
|
154
|
+
execFile("open", [url]);
|
|
155
|
+
}
|
|
156
|
+
else if (process.platform === "win32") {
|
|
157
|
+
// `start` is a cmd.exe built-in, not a standalone executable.
|
|
158
|
+
execFile("cmd", ["/c", "start", "", url]);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
execFile("xdg-open", [url]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function waitForCode(port, redirectUri, timeoutMs) {
|
|
165
|
+
// Import before entering the Promise constructor to avoid the async-executor
|
|
166
|
+
// anti-pattern: if the dynamic import throws, the rejection propagates through
|
|
167
|
+
// this async function rather than escaping an async Promise constructor.
|
|
168
|
+
const { createServer } = await import("node:http");
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const timer = setTimeout(() => {
|
|
171
|
+
server.close();
|
|
172
|
+
reject(new Error(`PKCE authentication timed out after ${timeoutMs}ms`));
|
|
173
|
+
}, timeoutMs);
|
|
174
|
+
const server = createServer((req, res) => {
|
|
175
|
+
try {
|
|
176
|
+
const reqUrl = new URL(req.url ?? "/", redirectUri);
|
|
177
|
+
const code = reqUrl.searchParams.get("code");
|
|
178
|
+
const error = reqUrl.searchParams.get("error");
|
|
179
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
180
|
+
res.end("<html><body><p>Authentication complete. You can close this tab.</p></body></html>");
|
|
181
|
+
server.close();
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
if (error) {
|
|
184
|
+
reject(new OAuthError(error, reqUrl.searchParams.get("error_description") ?? error));
|
|
185
|
+
}
|
|
186
|
+
else if (code) {
|
|
187
|
+
resolve(code);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
reject(new Error("No authorization code in redirect"));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
server.close();
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
reject(e);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
server.listen(port, "localhost");
|
|
200
|
+
server.on("error", (err) => {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
reject(new Error(`Failed to start loopback server on port ${port}: ${err.message}`));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/pkce.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keycardai/oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "[Preview] OAuth 2.0 primitives for Keycard: JWKS keyring, JWT signing/verification, server-tier token verifier, AccessContext, ClientSecret credentials, and impersonation via RFC 8693 token exchange",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -89,6 +89,11 @@
|
|
|
89
89
|
"import": "./dist/esm/server/clientSecret.js",
|
|
90
90
|
"require": "./dist/cjs/server/clientSecret.js",
|
|
91
91
|
"types": "./dist/esm/server/clientSecret.d.ts"
|
|
92
|
+
},
|
|
93
|
+
"./pkce": {
|
|
94
|
+
"import": "./dist/esm/pkce.js",
|
|
95
|
+
"require": "./dist/cjs/pkce.js",
|
|
96
|
+
"types": "./dist/esm/pkce.d.ts"
|
|
92
97
|
}
|
|
93
98
|
},
|
|
94
99
|
"files": [
|
|
@@ -112,6 +117,7 @@
|
|
|
112
117
|
},
|
|
113
118
|
"devDependencies": {
|
|
114
119
|
"@jest/globals": "^30.0.4",
|
|
120
|
+
"@types/node": "^25.6.0",
|
|
115
121
|
"jest": "^30.0.4",
|
|
116
122
|
"ts-jest": "^29.4.0",
|
|
117
123
|
"typescript": "^5.8.3"
|