Commit dddca9a
authored
[v8] feat: add PKCE support for public clients; remove /client entry point (#1435)
## Description
Enable PKCE authentication for both public and confidential clients.
### Changes
- **API key now optional**: Initialize with just `clientId` for PKCE
mode: `new WorkOS({ clientId: 'client_...' })`
- **New helper method**: `getAuthorizationUrlWithPKCE()` - generates
PKCE internally, returns `{ url, state, codeVerifier }`
- **Enhanced exchange**: `authenticateWithCode()` auto-detects client
mode based on available credentials
- **Manual PKCE option**: `workos.pkce.generate()` +
`getAuthorizationUrl()` for advanced use cases
- **Non-breaking**: Existing `getAuthorizationUrl()` unchanged, still
returns URL string
### PKCE with Confidential Clients (OAuth 2.1 Best Practice)
Server-side apps can use PKCE alongside the client secret for defense in
depth:
```ts
const workos = new WorkOS('sk_...'); // With API key
const { url, codeVerifier } = await workos.userManagement.getAuthorizationUrlWithPKCE({
provider: 'authkit',
redirectUri: 'https://example.com/callback',
clientId: 'client_...',
});
// Both client_secret AND code_verifier will be sent
const { accessToken } = await workos.userManagement.authenticateWithCode({
code: authorizationCode,
codeVerifier,
clientId: 'client_...',
});
```
The auto-detection logic:
| API Key | codeVerifier | Behavior |
|---------|--------------|----------|
| ✅ | ✅ | Send both `client_secret` AND `code_verifier` (confidential +
PKCE) |
| ✅ | ❌ | Send `client_secret` only (traditional confidential client) |
| ❌ | ✅ | Send `code_verifier` only (public client) |
| ❌ | ❌ | Error |
### Removed: `@workos-inc/node/client` export
The separate `/client` entry point has been removed. Instead of:
```ts
// Old approach - standalone functions
import { userManagement } from '@workos-inc/node/client';
const url = userManagement.getAuthorizationUrl({ ... });
```
Use the standard SDK without an API key:
```ts
// New approach - consistent with rest of SDK
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS({ clientId: 'client_...' });
const url = workos.userManagement.getAuthorizationUrl({ ... });
```
This provides a single, consistent API surface rather than two parallel
approaches.
### Public Client Usage
```ts
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS({ clientId: 'client_...' });
// Step 1: Get authorization URL with auto-generated PKCE
const { url, state, codeVerifier } = await workos.userManagement.getAuthorizationUrlWithPKCE({
redirectUri: 'myapp://callback',
provider: 'authkit',
});
// Store codeVerifier securely, then redirect user to url
// Step 2: Exchange code for tokens
const { accessToken, refreshToken, user } = await workos.userManagement.authenticateWithCode({
code: authCode,
codeVerifier,
});
```
### Methods Available Without API Key
| Method | Description |
|--------|-------------|
| `userManagement.getAuthorizationUrlWithPKCE()` | Build OAuth URL with
auto-generated PKCE |
| `userManagement.getAuthorizationUrl()` | Build OAuth URL (with manual
PKCE params) |
| `userManagement.authenticateWithCode()` | Exchange code + verifier for
tokens |
| `userManagement.authenticateWithCodeAndVerifier()` | Exchange code +
verifier for tokens (explicit) |
| `userManagement.authenticateWithRefreshToken()` | Refresh tokens |
| `userManagement.getLogoutUrl()` | Build logout redirect URL |
| `userManagement.getJwksUrl()` | Get JWKS URL for local JWT validation
|
| `workos.pkce.generate()` | Generate PKCE code verifier and challenge |1 parent 4e90607 commit dddca9a
File tree
49 files changed
+1838
-820
lines changed- src
- client
- common
- exceptions
- interfaces
- pkce
- sso
- __snapshots__
- interfaces
- user-management
- __snapshots__
- interfaces
- serializers
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
49 files changed
+1838
-820
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
40 | 94 | | |
41 | 95 | | |
42 | 96 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| 60 | + | |
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
| |||
110 | 111 | | |
111 | 112 | | |
112 | 113 | | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | 114 | | |
125 | 115 | | |
126 | 116 | | |
| |||
This file was deleted.
This file was deleted.
This file was deleted.
0 commit comments