@bb-labs/pkce 0.0.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 +125 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/dist/pkce/expo.d.ts +10 -0
- package/dist/pkce/expo.js +55 -0
- package/dist/pkce/nodejs.d.ts +10 -0
- package/dist/pkce/nodejs.js +49 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @bb-labs/pkce
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-dependency PKCE (Proof Key for Code Exchange) library for OAuth 2.0 that works across platforms.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Zero Dependencies**: No external crypto libraries required
|
|
8
|
+
- 🔄 **Cross-Platform**: Works in React Native (expo-crypto), Node.js (built-in crypto), and browsers
|
|
9
|
+
- 🚀 **Auto-Detection**: Automatically uses the best available crypto implementation
|
|
10
|
+
- 📦 **Tree-Shakable**: Import only what you need
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @bb-labs/pkce
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Automatic Environment Detection (Recommended)
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { generateCodeVerifier, createCodeChallenge } from "@bb-labs/pkce";
|
|
24
|
+
|
|
25
|
+
// Generate a cryptographically secure code verifier
|
|
26
|
+
const verifier = generateCodeVerifier();
|
|
27
|
+
|
|
28
|
+
// Create the corresponding code challenge
|
|
29
|
+
const challenge = await createCodeChallenge(verifier);
|
|
30
|
+
|
|
31
|
+
console.log("Verifier:", verifier);
|
|
32
|
+
console.log("Challenge:", challenge);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Platform-Specific Imports
|
|
36
|
+
|
|
37
|
+
#### React Native / Expo
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { generateCodeVerifier, createCodeChallenge } from "@bb-labs/pkce/expo";
|
|
41
|
+
|
|
42
|
+
// Make sure expo-crypto is installed in your React Native app
|
|
43
|
+
// npm install expo-crypto
|
|
44
|
+
|
|
45
|
+
const verifier = generateCodeVerifier();
|
|
46
|
+
const challenge = await createCodeChallenge(verifier);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Node.js
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { generateCodeVerifier, createCodeChallenge } from "@bb-labs/pkce/nodejs";
|
|
53
|
+
|
|
54
|
+
const verifier = generateCodeVerifier();
|
|
55
|
+
const challenge = await createCodeChallenge(verifier);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### `generateCodeVerifier(): string`
|
|
61
|
+
|
|
62
|
+
Generates a cryptographically secure random code verifier (43-128 characters, base64url encoded).
|
|
63
|
+
|
|
64
|
+
### `createCodeChallenge(verifier: string): Promise<string>`
|
|
65
|
+
|
|
66
|
+
Creates a code challenge by SHA-256 hashing the verifier and base64url encoding the result.
|
|
67
|
+
|
|
68
|
+
## Requirements
|
|
69
|
+
|
|
70
|
+
### React Native / Expo Apps
|
|
71
|
+
|
|
72
|
+
- Install `expo-crypto`: `npm install expo-crypto`
|
|
73
|
+
- The library will automatically detect and use expo-crypto
|
|
74
|
+
|
|
75
|
+
### Node.js Apps
|
|
76
|
+
|
|
77
|
+
- Node.js built-in `crypto` module (available in all modern Node.js versions)
|
|
78
|
+
- No additional dependencies required
|
|
79
|
+
|
|
80
|
+
### Browser Apps
|
|
81
|
+
|
|
82
|
+
- Modern browsers with Web Crypto API support
|
|
83
|
+
- Or use a polyfill for older browsers
|
|
84
|
+
|
|
85
|
+
## PKCE Flow Example
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { generateCodeVerifier, createCodeChallenge } from "@bb-labs/pkce";
|
|
89
|
+
|
|
90
|
+
// 1. Generate verifier and challenge
|
|
91
|
+
const verifier = generateCodeVerifier();
|
|
92
|
+
const challenge = await createCodeChallenge(verifier);
|
|
93
|
+
|
|
94
|
+
// 2. Send challenge to authorization server
|
|
95
|
+
const authUrl =
|
|
96
|
+
`https://auth.example.com/oauth/authorize?` +
|
|
97
|
+
`client_id=your_client_id&` +
|
|
98
|
+
`redirect_uri=your_redirect_uri&` +
|
|
99
|
+
`response_type=code&` +
|
|
100
|
+
`code_challenge=${challenge}&` +
|
|
101
|
+
`code_challenge_method=S256`;
|
|
102
|
+
|
|
103
|
+
// 3. After user authorization, exchange code for tokens using the verifier
|
|
104
|
+
const tokenResponse = await fetch("https://auth.example.com/oauth/token", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: new URLSearchParams({
|
|
107
|
+
grant_type: "authorization_code",
|
|
108
|
+
code: authorizationCode,
|
|
109
|
+
redirect_uri: redirectUri,
|
|
110
|
+
client_id: clientId,
|
|
111
|
+
code_verifier: verifier, // ← Send the original verifier
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Error Handling
|
|
117
|
+
|
|
118
|
+
The library will throw descriptive errors if no suitable crypto implementation is found:
|
|
119
|
+
|
|
120
|
+
- React Native: `"expo-crypto is required for React Native PKCE operations. Install expo-crypto or use a different PKCE implementation."`
|
|
121
|
+
- Node.js: `"Node.js crypto is required for server-side PKCE operations."`
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Direct exports for specific platforms
|
|
2
|
+
export * as expo from "./pkce/expo";
|
|
3
|
+
export * as nodejs from "./pkce/nodejs";
|
|
4
|
+
// Default export - try to auto-detect environment
|
|
5
|
+
let defaultExport;
|
|
6
|
+
try {
|
|
7
|
+
// Try React Native/expo first
|
|
8
|
+
require("expo-crypto");
|
|
9
|
+
defaultExport = require("./pkce/expo");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
try {
|
|
13
|
+
// Try Node.js crypto
|
|
14
|
+
require("crypto");
|
|
15
|
+
defaultExport = require("./pkce/nodejs");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Fallback - neither crypto is available
|
|
19
|
+
throw new Error("No suitable crypto implementation found. Install expo-crypto for React Native or use Node.js.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export const generateCodeVerifier = defaultExport.generateCodeVerifier;
|
|
23
|
+
export const createCodeChallenge = defaultExport.createCodeChallenge;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a cryptographically secure random code verifier for PKCE
|
|
3
|
+
* Uses expo-crypto if available, otherwise throws an error
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateCodeVerifier(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a code challenge from a code verifier using SHA-256
|
|
8
|
+
* Uses expo-crypto if available, otherwise throws an error
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCodeChallenge(verifier: string): Promise<string>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a cryptographically secure random code verifier for PKCE
|
|
3
|
+
* Uses expo-crypto if available, otherwise throws an error
|
|
4
|
+
*/
|
|
5
|
+
export function generateCodeVerifier() {
|
|
6
|
+
try {
|
|
7
|
+
const Crypto = require("expo-crypto");
|
|
8
|
+
// Generate 32 bytes (256 bits) of random data
|
|
9
|
+
const randomBytes = Crypto.getRandomBytes(32);
|
|
10
|
+
// Convert to base64url encoding (RFC 4648)
|
|
11
|
+
return base64UrlEncode(randomBytes);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error("expo-crypto is required for React Native PKCE operations. Install expo-crypto or use a different PKCE implementation.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a code challenge from a code verifier using SHA-256
|
|
19
|
+
* Uses expo-crypto if available, otherwise throws an error
|
|
20
|
+
*/
|
|
21
|
+
export async function createCodeChallenge(verifier) {
|
|
22
|
+
try {
|
|
23
|
+
const Crypto = require("expo-crypto");
|
|
24
|
+
// Hash the verifier using SHA-256
|
|
25
|
+
const hashBuffer = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, verifier, {
|
|
26
|
+
encoding: Crypto.CryptoEncoding.BASE64,
|
|
27
|
+
});
|
|
28
|
+
// Return base64url encoded hash
|
|
29
|
+
return base64UrlEncode(hashBuffer);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new Error("expo-crypto is required for React Native PKCE operations. Install expo-crypto or use a different PKCE implementation.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Converts input to base64url encoding (RFC 4648)
|
|
37
|
+
* Replaces '+' with '-', '/' with '_', and removes padding
|
|
38
|
+
*/
|
|
39
|
+
function base64UrlEncode(input) {
|
|
40
|
+
let base64;
|
|
41
|
+
if (typeof input === "string") {
|
|
42
|
+
// If input is already a base64 string, use it directly
|
|
43
|
+
base64 = input;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Convert Uint8Array to base64
|
|
47
|
+
base64 = "";
|
|
48
|
+
for (let i = 0; i < input.length; i++) {
|
|
49
|
+
base64 += String.fromCharCode(input[i]);
|
|
50
|
+
}
|
|
51
|
+
base64 = btoa(base64);
|
|
52
|
+
}
|
|
53
|
+
// Convert to base64url
|
|
54
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a cryptographically secure random code verifier for PKCE
|
|
3
|
+
* Node.js implementation using built-in crypto
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateCodeVerifier(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a code challenge from a code verifier using SHA-256
|
|
8
|
+
* Node.js implementation using built-in crypto
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCodeChallenge(verifier: string): Promise<string>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a cryptographically secure random code verifier for PKCE
|
|
3
|
+
* Node.js implementation using built-in crypto
|
|
4
|
+
*/
|
|
5
|
+
export function generateCodeVerifier() {
|
|
6
|
+
try {
|
|
7
|
+
const { randomBytes } = require("crypto");
|
|
8
|
+
// Generate 32 bytes (256 bits) of random data
|
|
9
|
+
const randomBytesData = randomBytes(32);
|
|
10
|
+
// Convert to base64url encoding (RFC 4648)
|
|
11
|
+
return base64UrlEncode(randomBytesData);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error("Node.js crypto is required for server-side PKCE operations.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a code challenge from a code verifier using SHA-256
|
|
19
|
+
* Node.js implementation using built-in crypto
|
|
20
|
+
*/
|
|
21
|
+
export async function createCodeChallenge(verifier) {
|
|
22
|
+
try {
|
|
23
|
+
const { createHash } = require("crypto");
|
|
24
|
+
// Hash the verifier using SHA-256
|
|
25
|
+
const hash = createHash("sha256").update(verifier).digest();
|
|
26
|
+
// Return base64url encoded hash
|
|
27
|
+
return base64UrlEncode(hash);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error("Node.js crypto is required for server-side PKCE operations.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Converts input to base64url encoding (RFC 4648)
|
|
35
|
+
* Replaces '+' with '-', '/' with '_', and removes padding
|
|
36
|
+
*/
|
|
37
|
+
function base64UrlEncode(input) {
|
|
38
|
+
let base64;
|
|
39
|
+
if (typeof input === "string") {
|
|
40
|
+
// If input is already a base64 string, use it directly
|
|
41
|
+
base64 = input;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Convert Buffer to base64
|
|
45
|
+
base64 = input.toString("base64");
|
|
46
|
+
}
|
|
47
|
+
// Convert to base64url
|
|
48
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bb-labs/pkce",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A library for PKCE",
|
|
5
|
+
"homepage": "https://github.com/beepbop-labs/pkce",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pkce",
|
|
8
|
+
"pkce-challenge",
|
|
9
|
+
"pkce-verifier"
|
|
10
|
+
],
|
|
11
|
+
"author": "Beepbop",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/beepbop-labs/pkce.git"
|
|
16
|
+
},
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
25
|
+
"pack": "npm run build && npm pack --pack-destination ./archive/"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/bun": "latest"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"typescript": "^5"
|
|
32
|
+
}
|
|
33
|
+
}
|