@memberjunction/auth-providers 5.43.0 → 5.44.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +17 -1
- package/WORKOS.md +256 -0
- package/dist/AuthProviderFactory.d.ts +1 -0
- package/dist/AuthProviderFactory.d.ts.map +1 -1
- package/dist/AuthProviderFactory.js +1 -0
- package/dist/AuthProviderFactory.js.map +1 -1
- package/dist/providers/WorkOSProvider.d.ts +48 -0
- package/dist/providers/WorkOSProvider.d.ts.map +1 -0
- package/dist/providers/WorkOSProvider.js +84 -0
- package/dist/providers/WorkOSProvider.js.map +1 -0
- package/package.json +3 -3
- package/src/AuthProviderFactory.ts +1 -0
- package/src/__tests__/WorkOSProvider.test.ts +169 -0
- package/src/providers/WorkOSProvider.ts +78 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @memberjunction/auth-providers
|
|
2
2
|
|
|
3
|
+
## 5.44.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2f9b863: Add WorkOS (AuthKit) as a first-class authentication provider — end to end, server-side JWT validation and browser-side login. A deployment can now set `type: 'workos'` (server) / `AUTH_TYPE: 'workos'` (browser) and authenticate users through WorkOS just like Auth0, Okta, MSAL, Cognito, or Google.
|
|
8
|
+
- **Server** (`@memberjunction/auth-providers`): `WorkOSProvider` extends `BaseAuthProvider`, registered via `@RegisterClass(BaseAuthProvider, 'workos')`. Maps AuthKit JWT claims to `AuthUserInfo` (with graceful fallbacks) and validates `clientId`; issuer matching, JWKS caching, and retry/backoff are inherited. Wired into `AuthProviderFactory`.
|
|
9
|
+
- **Client** (`@memberjunction/ng-auth-services`): `MJWorkOSProvider` extends `MJAuthBase`, registered via `@RegisterClass(MJAuthBase, 'workos')`. Wraps the `@workos-inc/authkit-js` SDK (`createClient`/`signIn`/`signOut`/`getUser`/`getAccessToken`) behind the standardized provider contract with semantic error classification.
|
|
10
|
+
- **Core** (`@memberjunction/core`): `AUTH_PROVIDER_TYPES` gains `WORKOS: 'workos'`.
|
|
11
|
+
- **Env typing** (`@memberjunction/ng-bootstrap`): the `AUTH_TYPE` union gains `'workos'`, plus `WORKOS_CLIENTID` / `WORKOS_REDIRECT_URI` / `WORKOS_API_HOSTNAME` / `WORKOS_DEV_MODE` keys.
|
|
12
|
+
|
|
13
|
+
Includes a full end-to-end integration guide (`packages/AuthProviders/WORKOS.md`) covering the two WorkOS-specific gotchas: the required `email` JWT Template (AuthKit access tokens omit email, which MJ keys users on) and matching the enforced `aud` claim. Additive only.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [5396d90]
|
|
18
|
+
- Updated dependencies [7279819]
|
|
19
|
+
- Updated dependencies [d44e430]
|
|
20
|
+
- Updated dependencies [6f74b17]
|
|
21
|
+
- Updated dependencies [2f9b863]
|
|
22
|
+
- @memberjunction/core@5.44.0
|
|
23
|
+
- @memberjunction/global@5.44.0
|
|
24
|
+
|
|
3
25
|
## 5.43.0
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ This package gives the MJ server (and any other Node.js consumer) a uniform, plu
|
|
|
9
9
|
- Extract a normalized `AuthUserInfo` from provider-specific claim shapes
|
|
10
10
|
- Register additional providers at runtime via the MJ class-factory system
|
|
11
11
|
|
|
12
|
-
It ships with first-class support for **Auth0**, **Microsoft Entra ID / MSAL**, **Okta**, **AWS Cognito**,
|
|
12
|
+
It ships with first-class support for **Auth0**, **Microsoft Entra ID / MSAL**, **Okta**, **AWS Cognito**, **Google Identity Platform**, and **WorkOS (AuthKit)**, and is the extension point used to plug custom providers into [`@memberjunction/server`](../MJServer/README.md).
|
|
13
|
+
|
|
14
|
+
> **Integrating WorkOS?** See the dedicated end-to-end guide: **[WORKOS.md](WORKOS.md)** — it covers the browser + server setup and the two WorkOS-specific gotchas (the required `email` JWT Template and matching the `aud` claim).
|
|
13
15
|
|
|
14
16
|
## When to use this package
|
|
15
17
|
|
|
@@ -67,6 +69,7 @@ This package is a Node.js / server-side package. It depends on:
|
|
|
67
69
|
│ ├─ @RegisterClass(BaseAuthProvider, 'okta') │
|
|
68
70
|
│ ├─ @RegisterClass(BaseAuthProvider, 'cognito') │
|
|
69
71
|
│ ├─ @RegisterClass(BaseAuthProvider, 'google') │
|
|
72
|
+
│ ├─ @RegisterClass(BaseAuthProvider, 'workos') │
|
|
70
73
|
│ └─ @RegisterClass(BaseAuthProvider, 'your-custom') │
|
|
71
74
|
└──────────────────────────────────────────────────────────────────┘
|
|
72
75
|
```
|
|
@@ -95,6 +98,7 @@ Each built-in provider is registered with the MJ class factory under a lowercase
|
|
|
95
98
|
| `okta` | `OktaProvider` | `clientId`, `domain` |
|
|
96
99
|
| `cognito` | `CognitoProvider` | `clientId`, `region`, `userPoolId` |
|
|
97
100
|
| `google` | `GoogleProvider` | `clientId` |
|
|
101
|
+
| `workos` | `WorkOSProvider` | `clientId` (see [WORKOS.md](WORKOS.md) for the required `email` JWT Template + `aud`) |
|
|
98
102
|
|
|
99
103
|
Every provider also requires the base fields: `name`, `type`, `issuer`, `audience`, `jwksUri`. See [`AuthProviderConfig`](../MJCore/src/generic/authTypes.ts) for the full shape.
|
|
100
104
|
|
|
@@ -124,11 +128,23 @@ module.exports = {
|
|
|
124
128
|
audience: 'https://api.example.com',
|
|
125
129
|
jwksUri: 'https://tenant.auth0.com/.well-known/jwks.json',
|
|
126
130
|
},
|
|
131
|
+
{
|
|
132
|
+
name: 'workos-prod',
|
|
133
|
+
type: 'workos',
|
|
134
|
+
clientId: process.env.WORKOS_CLIENT_ID, // client_01H...
|
|
135
|
+
issuer: `https://api.workos.com/user_management/${process.env.WORKOS_CLIENT_ID}`,
|
|
136
|
+
jwksUri: `https://api.workos.com/sso/jwks/${process.env.WORKOS_CLIENT_ID}`,
|
|
137
|
+
audience: process.env.WORKOS_CLIENT_ID, // must match the token's `aud` — see WORKOS.md
|
|
138
|
+
},
|
|
127
139
|
// ...add more providers here
|
|
128
140
|
],
|
|
129
141
|
};
|
|
130
142
|
```
|
|
131
143
|
|
|
144
|
+
> **WorkOS needs two extra steps** beyond this config — an `email` JWT Template (its access tokens
|
|
145
|
+
> omit email, which MJ keys users on) and matching the `aud` claim. The full walkthrough is in
|
|
146
|
+
> **[WORKOS.md](WORKOS.md)**.
|
|
147
|
+
|
|
132
148
|
> **Multiple audiences on the same issuer.** When two MJ apps share an Auth0 domain but use different client IDs, register both as separate entries — `AuthProviderFactory.getAllByIssuer()` returns every match so the validator can try each audience.
|
|
133
149
|
|
|
134
150
|
## Usage
|
package/WORKOS.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# WorkOS (AuthKit) Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide walks you through wiring **WorkOS AuthKit** into MemberJunction end to end —
|
|
4
|
+
browser login and server-side token validation — so users sign in through WorkOS exactly like
|
|
5
|
+
they would through Auth0, Okta, MSAL, Cognito, or Google.
|
|
6
|
+
|
|
7
|
+
WorkOS is consumed as an OIDC/JWT identity provider. MemberJunction does **not** replace WorkOS;
|
|
8
|
+
WorkOS handles authentication (hosted login, SSO, MFA, Directory Sync) and MJ handles everything
|
|
9
|
+
after the user is identified (entities, permissions, agents, audit).
|
|
10
|
+
|
|
11
|
+
> **TL;DR — the one thing that trips everyone up:** a WorkOS AuthKit **access token does not
|
|
12
|
+
> include the user's email by default**, and MemberJunction resolves users by email. You must add
|
|
13
|
+
> an `email` claim with a [WorkOS **JWT Template**](#step-3--add-an-email-claim-jwt-template).
|
|
14
|
+
> Skip this and every login will fail user lookup on the server. See
|
|
15
|
+
> [Step 3](#step-3--add-an-email-claim-jwt-template).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of contents
|
|
20
|
+
|
|
21
|
+
1. [How it fits together](#how-it-fits-together)
|
|
22
|
+
2. [What's in this package](#whats-in-this-package)
|
|
23
|
+
3. [Prerequisites](#prerequisites)
|
|
24
|
+
4. [Step 1 — Configure the WorkOS dashboard](#step-1--configure-the-workos-dashboard)
|
|
25
|
+
5. [Step 2 — Configure the MJ server (`mj.config.cjs`)](#step-2--configure-the-mj-server-mjconfigcjs)
|
|
26
|
+
6. [Step 3 — Add an `email` claim (JWT Template)](#step-3--add-an-email-claim-jwt-template)
|
|
27
|
+
7. [Step 4 — Configure the browser (MJExplorer)](#step-4--configure-the-browser-mjexplorer)
|
|
28
|
+
8. [Token claims reference](#token-claims-reference)
|
|
29
|
+
9. [How validation works under the hood](#how-validation-works-under-the-hood)
|
|
30
|
+
10. [Troubleshooting](#troubleshooting)
|
|
31
|
+
11. [Scope & non-goals](#scope--non-goals)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## How it fits together
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌─ Browser (MJExplorer) ─────────────────┐ ┌─ MJ GraphQL Server ─────────────────────┐
|
|
39
|
+
│ │ │ │
|
|
40
|
+
│ MJWorkOSProvider (extends MJAuthBase) │ │ WorkOSProvider (extends BaseAuthProvider)│
|
|
41
|
+
│ @RegisterClass(MJAuthBase,'workos') │ │ @RegisterClass(BaseAuthProvider,'workos')│
|
|
42
|
+
│ │ │ │ ▲ │
|
|
43
|
+
│ @workos-inc/authkit-js │ │ │ jwks-rsa + jsonwebtoken │
|
|
44
|
+
│ │ signIn → hosted login │ │ │ (verify signature, iss, aud) │
|
|
45
|
+
│ │ getUser() (email for display)│ │ │ │
|
|
46
|
+
│ └─ getAccessToken() ── Bearer ───┼────────┼──▶ AuthProviderFactory.getByIssuer(iss) │
|
|
47
|
+
│ │ │ → WorkOSProvider.extractUserInfo() │
|
|
48
|
+
│ │ │ → verifyUserRecord(email) │
|
|
49
|
+
└─────────────────────────────────────────┘ └──────────────────────────────────────────┘
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Both ends register under the same lowercase key, `workos`, through MJ's class factory. The server
|
|
53
|
+
trusts the issuer/JWKS to verify the signature; the browser SDK manages the session and silent
|
|
54
|
+
token refresh.
|
|
55
|
+
|
|
56
|
+
## What's in this package
|
|
57
|
+
|
|
58
|
+
| Side | Class | File | Registers as |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| Server | `WorkOSProvider` | [`src/providers/WorkOSProvider.ts`](src/providers/WorkOSProvider.ts) | `@RegisterClass(BaseAuthProvider, 'workos')` |
|
|
61
|
+
| Browser | `MJWorkOSProvider` | `@memberjunction/ng-auth-services` → `src/lib/providers/mjexplorer-workos-provider.service.ts` | `@RegisterClass(MJAuthBase, 'workos')` |
|
|
62
|
+
|
|
63
|
+
The server provider is registered automatically (it's imported by
|
|
64
|
+
[`AuthProviderFactory.ts`](src/AuthProviderFactory.ts)); you only need to add a config entry.
|
|
65
|
+
|
|
66
|
+
## Prerequisites
|
|
67
|
+
|
|
68
|
+
- A [WorkOS](https://workos.com) account with **AuthKit / User Management** enabled.
|
|
69
|
+
- Your **Client ID** (looks like `client_01H...`) from the WorkOS dashboard.
|
|
70
|
+
- The browser app must depend on `@workos-inc/authkit-js` (already declared as a peer dependency
|
|
71
|
+
of `@memberjunction/ng-auth-services`, and a direct dependency of MJExplorer).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Step 1 — Configure the WorkOS dashboard
|
|
76
|
+
|
|
77
|
+
1. In the WorkOS dashboard, go to **Authentication → AuthKit** and enable it.
|
|
78
|
+
2. Under **Redirects**, add your app's redirect URI (e.g. `http://localhost:4200` for local
|
|
79
|
+
development, plus your production origin). This is where WorkOS returns the user after login.
|
|
80
|
+
3. Set a **Logout URI** (the page WorkOS returns to after sign-out — typically the same origin).
|
|
81
|
+
4. Note your **Client ID**. You'll use it in three places: the server `issuer`/`jwksUri`, the
|
|
82
|
+
server `clientId`, and the browser `WORKOS_CLIENTID`.
|
|
83
|
+
|
|
84
|
+
## Step 2 — Configure the MJ server (`mj.config.cjs`)
|
|
85
|
+
|
|
86
|
+
Add a `workos` entry to `authProviders`. The `issuer` and `jwksUri` are derived from your Client
|
|
87
|
+
ID:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// mj.config.cjs
|
|
91
|
+
module.exports = {
|
|
92
|
+
authProviders: [
|
|
93
|
+
{
|
|
94
|
+
name: 'workos-prod',
|
|
95
|
+
type: 'workos',
|
|
96
|
+
clientId: process.env.WORKOS_CLIENT_ID, // client_01H...
|
|
97
|
+
issuer: `https://api.workos.com/user_management/${process.env.WORKOS_CLIENT_ID}`,
|
|
98
|
+
jwksUri: `https://api.workos.com/sso/jwks/${process.env.WORKOS_CLIENT_ID}`,
|
|
99
|
+
// The audience MUST match the token's `aud` claim — see the note below.
|
|
100
|
+
audience: process.env.WORKOS_CLIENT_ID,
|
|
101
|
+
},
|
|
102
|
+
// ...other providers can coexist; the factory routes by the token's `iss` claim
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Getting `audience` right
|
|
108
|
+
|
|
109
|
+
MemberJunction **always enforces the `aud` claim** during verification. WorkOS sets `aud`
|
|
110
|
+
automatically (you cannot override it via a JWT Template — it's a reserved claim):
|
|
111
|
+
|
|
112
|
+
- **No Resource Indicators configured (default):** WorkOS uses a default audience unique to your
|
|
113
|
+
WorkOS environment. Decode a real access token (e.g. at [jwt.io](https://jwt.io)) and copy the
|
|
114
|
+
exact `aud` value into your `audience` config.
|
|
115
|
+
- **Resource Indicators configured:** the `aud` matches the requested resource — set `audience`
|
|
116
|
+
to that resource identifier.
|
|
117
|
+
|
|
118
|
+
> If `audience` does not match the token's `aud`, verification fails with
|
|
119
|
+
> `jwt audience invalid`. This is the second most common misconfiguration after the missing email
|
|
120
|
+
> claim.
|
|
121
|
+
|
|
122
|
+
## Step 3 — Add an `email` claim (JWT Template)
|
|
123
|
+
|
|
124
|
+
**This step is required.** A WorkOS AuthKit access token carries identity/session claims
|
|
125
|
+
(`sub`, `sid`, `org_id`, `role`, `permissions`) but **not** the user's email. MemberJunction
|
|
126
|
+
looks the user up by email server-side, so you must add it.
|
|
127
|
+
|
|
128
|
+
In the WorkOS dashboard, go to **Authentication → Sessions → JWT Template** (or
|
|
129
|
+
**JWT Templates**) and add:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"email": "{{user.email}}",
|
|
134
|
+
"given_name": "{{user.first_name}}",
|
|
135
|
+
"family_name": "{{user.last_name}}"
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- `email` is what MJ matches against the `User.Email` column — **required**.
|
|
140
|
+
- `given_name` / `family_name` are optional but recommended: they let MJ
|
|
141
|
+
[auto-provision new users](../MJServer/README.md) with proper names when
|
|
142
|
+
`userHandling.autoCreateNewUsers` is enabled.
|
|
143
|
+
- WorkOS JWT Templates drop any claim whose value renders to `null`, so users missing a first/last
|
|
144
|
+
name simply won't have those claims — the provider handles that gracefully (it falls back to
|
|
145
|
+
splitting a `name` claim, or to the email).
|
|
146
|
+
|
|
147
|
+
> **Why not just read email from the ID token?** The browser SDK's `getUser()` always has the
|
|
148
|
+
> email (used for display), but the **server** only sees the bearer **access token**. The JWT
|
|
149
|
+
> Template is what puts the email into that access token.
|
|
150
|
+
|
|
151
|
+
## Step 4 — Configure the browser (MJExplorer)
|
|
152
|
+
|
|
153
|
+
Set `AUTH_TYPE` to `workos` and provide the WorkOS environment keys. These flow into the provider
|
|
154
|
+
through `AuthServicesModule.forRoot(environment)`:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// environment.ts
|
|
158
|
+
export const environment = {
|
|
159
|
+
// ...
|
|
160
|
+
AUTH_TYPE: 'workos',
|
|
161
|
+
WORKOS_CLIENTID: 'client_01H...', // required
|
|
162
|
+
WORKOS_REDIRECT_URI: window.location.origin, // optional; defaults to window.location.origin
|
|
163
|
+
WORKOS_API_HOSTNAME: undefined, // optional; only for custom domains / proxies
|
|
164
|
+
WORKOS_DEV_MODE: false, // optional; localStorage session for local dev
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Env key | Required | Purpose |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `AUTH_TYPE` | ✅ | Must be `'workos'` to select this provider |
|
|
171
|
+
| `WORKOS_CLIENTID` | ✅ | Your AuthKit Client ID |
|
|
172
|
+
| `WORKOS_REDIRECT_URI` | — | Redirect target after login (default: `window.location.origin`) |
|
|
173
|
+
| `WORKOS_API_HOSTNAME` | — | Override the WorkOS API hostname (custom domains / proxy) |
|
|
174
|
+
| `WORKOS_DEV_MODE` | — | AuthKit dev mode — localStorage-backed session for local development |
|
|
175
|
+
|
|
176
|
+
That's it. On startup, `MJWorkOSProvider.initialize()` constructs the AuthKit client (which also
|
|
177
|
+
processes any pending redirect callback), establishes the session, and the rest of MJ works
|
|
178
|
+
unchanged — `getIdToken()` hands the access token to the GraphQL client as a Bearer token.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Token claims reference
|
|
183
|
+
|
|
184
|
+
A WorkOS AuthKit **access token** (what `getAccessToken()` returns and the server validates),
|
|
185
|
+
**after** adding the recommended JWT Template:
|
|
186
|
+
|
|
187
|
+
```jsonc
|
|
188
|
+
{
|
|
189
|
+
"iss": "https://api.workos.com/user_management/client_01H...",
|
|
190
|
+
"sub": "user_01HXYZ...", // WorkOS user id → AuthUserInfo.userId
|
|
191
|
+
"sid": "session_01H...", // session id
|
|
192
|
+
"jti": "01H...",
|
|
193
|
+
"aud": "<environment default or resource indicator>", // MJ enforces this
|
|
194
|
+
"org_id": "org_01H...", // present for organization-scoped sessions
|
|
195
|
+
"role": "admin", // organization-membership role
|
|
196
|
+
"permissions": ["posts:read"], // role permissions
|
|
197
|
+
"exp": 1717000000,
|
|
198
|
+
"iat": 1716996400,
|
|
199
|
+
|
|
200
|
+
// ── added by your JWT Template ──
|
|
201
|
+
"email": "ada@example.com", // REQUIRED for MJ user resolution
|
|
202
|
+
"given_name": "Ada",
|
|
203
|
+
"family_name": "Lovelace"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`WorkOSProvider.extractUserInfo()` maps this to MJ's `AuthUserInfo`:
|
|
208
|
+
|
|
209
|
+
| `AuthUserInfo` field | Source claim | Fallback |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| `email` | `email` | — (undefined if no JWT Template) |
|
|
212
|
+
| `firstName` | `given_name` | first word of `name` |
|
|
213
|
+
| `lastName` | `family_name` | second word of `name`, else first word |
|
|
214
|
+
| `fullName` | `name` | — |
|
|
215
|
+
| `preferredUsername` | `preferred_username` | `email` |
|
|
216
|
+
| `userId` | `sub` | — |
|
|
217
|
+
|
|
218
|
+
## How validation works under the hood
|
|
219
|
+
|
|
220
|
+
1. The browser sends `Authorization: Bearer <accessToken>` to the MJ GraphQL API.
|
|
221
|
+
2. MJServer decodes the token (without verifying) to read the `iss` claim.
|
|
222
|
+
3. `AuthProviderFactory.getByIssuer(iss)` resolves the `WorkOSProvider` instance (issuer matching
|
|
223
|
+
is case-insensitive and trailing-slash tolerant).
|
|
224
|
+
4. `jwt.verify()` validates the RS256 signature against the JWKS at
|
|
225
|
+
`https://api.workos.com/sso/jwks/<clientId>` (cached, with retry/backoff handled by
|
|
226
|
+
`BaseAuthProvider`), and checks `iss` + `aud`.
|
|
227
|
+
5. `WorkOSProvider.extractUserInfo(payload)` produces the normalized identity.
|
|
228
|
+
6. MJServer resolves/creates the `User` record by **email** and builds the request context.
|
|
229
|
+
|
|
230
|
+
See [`@memberjunction/auth-providers` README](README.md) and
|
|
231
|
+
[`@memberjunction/server`](../MJServer/README.md) for the shared validation pipeline.
|
|
232
|
+
|
|
233
|
+
## Troubleshooting
|
|
234
|
+
|
|
235
|
+
| Symptom | Likely cause | Fix |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| Server logs "user not found" / login bounces | Access token has no `email` claim | [Add the JWT Template](#step-3--add-an-email-claim-jwt-template) |
|
|
238
|
+
| `jwt audience invalid` | `audience` config ≠ token `aud` | Decode a real token, copy its `aud` into `audience` ([Step 2](#getting-audience-right)) |
|
|
239
|
+
| `No authentication provider found for issuer` | `issuer` config doesn't match the token | Ensure `issuer` is `https://api.workos.com/user_management/<clientId>` with your real Client ID |
|
|
240
|
+
| `jwt expired` right after login | Clock skew or stale token | The browser SDK auto-refreshes; ensure server time is correct |
|
|
241
|
+
| Login redirect loops | Redirect URI not registered | Add your exact origin under WorkOS dashboard → Redirects |
|
|
242
|
+
| New users not auto-created | Missing `given_name`/`family_name` or `autoCreateNewUsers` off | Add name claims to the JWT Template; check `userHandling` config |
|
|
243
|
+
|
|
244
|
+
## Scope & non-goals
|
|
245
|
+
|
|
246
|
+
- **In scope:** WorkOS **AuthKit / User Management** (OIDC/JWT bearer login + validation). This is
|
|
247
|
+
the same OIDC/JWT model every other MJ provider uses.
|
|
248
|
+
- **Out of scope (today):** WorkOS **SSO via SAML assertions** and **Directory Sync (SCIM)**
|
|
249
|
+
user provisioning as distinct flows. AuthKit can itself broker SSO connections behind its hosted
|
|
250
|
+
login, so SSO end-users still authenticate through the `workos` provider — but MJ does not parse
|
|
251
|
+
raw SAML assertions or run SCIM provisioning. Those would be a separate, larger effort.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
*Part of [`@memberjunction/auth-providers`](README.md) in the
|
|
256
|
+
[MemberJunction](https://github.com/MemberJunction/MJ) monorepo.*
|
|
@@ -6,6 +6,7 @@ import './providers/MSALProvider.js';
|
|
|
6
6
|
import './providers/OktaProvider.js';
|
|
7
7
|
import './providers/CognitoProvider.js';
|
|
8
8
|
import './providers/GoogleProvider.js';
|
|
9
|
+
import './providers/WorkOSProvider.js';
|
|
9
10
|
import './providers/MagicLinkProvider.js';
|
|
10
11
|
/**
|
|
11
12
|
* Factory and registry for managing authentication providers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProviderFactory.d.ts","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAe,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAY,aAAa,EAAc,MAAM,wBAAwB,CAAC;AAG7E,OAAO,8BAA8B,CAAC;AACtC,OAAO,6BAA6B,CAAC;AACrC,OAAO,6BAA6B,CAAC;AACrC,OAAO,gCAAgC,CAAC;AACxC,OAAO,+BAA+B,CAAC;AACvC,OAAO,kCAAkC,CAAC;AAE1C;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,aAAa,CAAC,mBAAmB,CAAC;IACzE,OAAO,CAAC,SAAS,CAAyC;IAC1D;;;;;;OAMG;IACH,OAAO,CAAC,WAAW,CAA6F;IAChH;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAiG;;IAMzH;;OAEG;IACH,WAAkB,QAAQ,IAAI,mBAAmB,CAEhD;IAED;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa;IAsBhE;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAiBvC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAmBtD;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE;IAqB/C;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIlD;;OAEG;IACH,eAAe,IAAI,aAAa,EAAE;IAIlC;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,MAAM,CAAC,0BAA0B,IAAI,MAAM,EAAE;IAW7C;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CASvD"}
|
|
1
|
+
{"version":3,"file":"AuthProviderFactory.d.ts","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAe,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAY,aAAa,EAAc,MAAM,wBAAwB,CAAC;AAG7E,OAAO,8BAA8B,CAAC;AACtC,OAAO,6BAA6B,CAAC;AACrC,OAAO,6BAA6B,CAAC;AACrC,OAAO,gCAAgC,CAAC;AACxC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,kCAAkC,CAAC;AAE1C;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,aAAa,CAAC,mBAAmB,CAAC;IACzE,OAAO,CAAC,SAAS,CAAyC;IAC1D;;;;;;OAMG;IACH,OAAO,CAAC,WAAW,CAA6F;IAChH;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAiG;;IAMzH;;OAEG;IACH,WAAkB,QAAQ,IAAI,mBAAmB,CAEhD;IAED;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa;IAsBhE;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAiBvC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAmBtD;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE;IAqB/C;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIlD;;OAEG;IACH,eAAe,IAAI,aAAa,EAAE;IAIlC;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,MAAM,CAAC,0BAA0B,IAAI,MAAM,EAAE;IAW7C;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CASvD"}
|
|
@@ -7,6 +7,7 @@ import './providers/MSALProvider.js';
|
|
|
7
7
|
import './providers/OktaProvider.js';
|
|
8
8
|
import './providers/CognitoProvider.js';
|
|
9
9
|
import './providers/GoogleProvider.js';
|
|
10
|
+
import './providers/WorkOSProvider.js';
|
|
10
11
|
import './providers/MagicLinkProvider.js';
|
|
11
12
|
/**
|
|
12
13
|
* Factory and registry for managing authentication providers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProviderFactory.js","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuthProviderFactory.js","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
3
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
4
|
+
/**
|
|
5
|
+
* WorkOS (AuthKit) authentication provider implementation.
|
|
6
|
+
*
|
|
7
|
+
* Validates JWT access tokens minted by WorkOS AuthKit / User Management. These tokens are
|
|
8
|
+
* signed with RS256 and verified against the per-environment JWKS endpoint
|
|
9
|
+
* (`https://api.workos.com/sso/jwks/<clientId>`); their `iss` claim is
|
|
10
|
+
* `https://api.workos.com/user_management/<clientId>`.
|
|
11
|
+
*
|
|
12
|
+
* ## Email is required — and is NOT in a WorkOS token by default
|
|
13
|
+
* MemberJunction resolves the signed-in user by **email**, but a WorkOS AuthKit access token
|
|
14
|
+
* only carries identity/session claims (`sub`, `sid`, `org_id`, `role`, `permissions`) out of
|
|
15
|
+
* the box — it does **not** include `email`. To use WorkOS with MJ you must add the email (and
|
|
16
|
+
* ideally the name) to the token via a WorkOS **JWT Template**:
|
|
17
|
+
*
|
|
18
|
+
* ```json
|
|
19
|
+
* {
|
|
20
|
+
* "email": "{{user.email}}",
|
|
21
|
+
* "given_name": "{{user.first_name}}",
|
|
22
|
+
* "family_name": "{{user.last_name}}"
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* See `WORKOS.md` in this package for the full, step-by-step integration guide.
|
|
27
|
+
*/
|
|
28
|
+
export declare class WorkOSProvider extends BaseAuthProvider {
|
|
29
|
+
constructor(config: AuthProviderConfig);
|
|
30
|
+
/**
|
|
31
|
+
* Extracts user information from a WorkOS AuthKit JWT payload.
|
|
32
|
+
*
|
|
33
|
+
* `email`, `given_name`, and `family_name` are expected to be supplied by a WorkOS JWT
|
|
34
|
+
* Template (see class docs). `sub` is the stable WorkOS user id (`user_...`). We fall back
|
|
35
|
+
* through standard OIDC claim names so a token shaped by a custom template (e.g. one that
|
|
36
|
+
* only sets `name`) still resolves a usable identity.
|
|
37
|
+
*/
|
|
38
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo;
|
|
39
|
+
/**
|
|
40
|
+
* Validates WorkOS-specific configuration.
|
|
41
|
+
*
|
|
42
|
+
* Beyond the base requirements (`name`, `issuer`, `audience`, `jwksUri`), WorkOS needs a
|
|
43
|
+
* `clientId` — it appears in both the issuer and JWKS URLs and identifies the AuthKit
|
|
44
|
+
* environment.
|
|
45
|
+
*/
|
|
46
|
+
validateConfig(): boolean;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=WorkOSProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkOSProvider.d.ts","sourceRoot":"","sources":["../../src/providers/WorkOSProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBACa,cAAe,SAAQ,gBAAgB;gBACtC,MAAM,EAAE,kBAAkB;IAItC;;;;;;;OAOG;IACH,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,YAAY;IAoBlD;;;;;;OAMG;IACH,cAAc,IAAI,OAAO;CAM1B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
11
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
12
|
+
/**
|
|
13
|
+
* WorkOS (AuthKit) authentication provider implementation.
|
|
14
|
+
*
|
|
15
|
+
* Validates JWT access tokens minted by WorkOS AuthKit / User Management. These tokens are
|
|
16
|
+
* signed with RS256 and verified against the per-environment JWKS endpoint
|
|
17
|
+
* (`https://api.workos.com/sso/jwks/<clientId>`); their `iss` claim is
|
|
18
|
+
* `https://api.workos.com/user_management/<clientId>`.
|
|
19
|
+
*
|
|
20
|
+
* ## Email is required — and is NOT in a WorkOS token by default
|
|
21
|
+
* MemberJunction resolves the signed-in user by **email**, but a WorkOS AuthKit access token
|
|
22
|
+
* only carries identity/session claims (`sub`, `sid`, `org_id`, `role`, `permissions`) out of
|
|
23
|
+
* the box — it does **not** include `email`. To use WorkOS with MJ you must add the email (and
|
|
24
|
+
* ideally the name) to the token via a WorkOS **JWT Template**:
|
|
25
|
+
*
|
|
26
|
+
* ```json
|
|
27
|
+
* {
|
|
28
|
+
* "email": "{{user.email}}",
|
|
29
|
+
* "given_name": "{{user.first_name}}",
|
|
30
|
+
* "family_name": "{{user.last_name}}"
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* See `WORKOS.md` in this package for the full, step-by-step integration guide.
|
|
35
|
+
*/
|
|
36
|
+
let WorkOSProvider = class WorkOSProvider extends BaseAuthProvider {
|
|
37
|
+
constructor(config) {
|
|
38
|
+
super(config);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts user information from a WorkOS AuthKit JWT payload.
|
|
42
|
+
*
|
|
43
|
+
* `email`, `given_name`, and `family_name` are expected to be supplied by a WorkOS JWT
|
|
44
|
+
* Template (see class docs). `sub` is the stable WorkOS user id (`user_...`). We fall back
|
|
45
|
+
* through standard OIDC claim names so a token shaped by a custom template (e.g. one that
|
|
46
|
+
* only sets `name`) still resolves a usable identity.
|
|
47
|
+
*/
|
|
48
|
+
extractUserInfo(payload) {
|
|
49
|
+
const email = payload.email;
|
|
50
|
+
const fullName = payload.name;
|
|
51
|
+
const firstName = payload.given_name;
|
|
52
|
+
const lastName = payload.family_name;
|
|
53
|
+
// WorkOS access tokens have no `preferred_username`; email is the natural handle.
|
|
54
|
+
const preferredUsername = payload.preferred_username || email;
|
|
55
|
+
// `sub` is the WorkOS user identifier (e.g. "user_01H...").
|
|
56
|
+
const userId = payload.sub;
|
|
57
|
+
return {
|
|
58
|
+
email,
|
|
59
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
60
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
61
|
+
fullName,
|
|
62
|
+
preferredUsername,
|
|
63
|
+
userId
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validates WorkOS-specific configuration.
|
|
68
|
+
*
|
|
69
|
+
* Beyond the base requirements (`name`, `issuer`, `audience`, `jwksUri`), WorkOS needs a
|
|
70
|
+
* `clientId` — it appears in both the issuer and JWKS URLs and identifies the AuthKit
|
|
71
|
+
* environment.
|
|
72
|
+
*/
|
|
73
|
+
validateConfig() {
|
|
74
|
+
const baseValid = super.validateConfig();
|
|
75
|
+
const hasClientId = !!this.config.clientId;
|
|
76
|
+
return baseValid && hasClientId;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
WorkOSProvider = __decorate([
|
|
80
|
+
RegisterClass(BaseAuthProvider, 'workos'),
|
|
81
|
+
__metadata("design:paramtypes", [Object])
|
|
82
|
+
], WorkOSProvider);
|
|
83
|
+
export { WorkOSProvider };
|
|
84
|
+
//# sourceMappingURL=WorkOSProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkOSProvider.js","sourceRoot":"","sources":["../../src/providers/WorkOSProvider.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,gBAAgB;IAClD,YAAY,MAA0B;QACpC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChB,CAAC;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,OAAmB;QACjC,MAAM,KAAK,GAAG,OAAO,CAAC,KAA2B,CAAC;QAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAA0B,CAAC;QACpD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAgC,CAAC;QAC3D,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAiC,CAAC;QAC3D,kFAAkF;QAClF,MAAM,iBAAiB,GAAG,OAAO,CAAC,kBAAwC,IAAI,KAAK,CAAC;QACpF,4DAA4D;QAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAyB,CAAC;QAEjD,OAAO;YACL,KAAK;YACL,SAAS,EAAE,SAAS,IAAI,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/C,QAAQ,EAAE,QAAQ,IAAI,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACxE,QAAQ;YACR,iBAAiB;YACjB,MAAM;SACP,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,cAAc;QACZ,MAAM,SAAS,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAE3C,OAAO,SAAS,IAAI,WAAW,CAAC;IAClC,CAAC;CACF,CAAA;AA9CY,cAAc;IAD1B,aAAa,CAAC,gBAAgB,EAAE,QAAQ,CAAC;;GAC7B,cAAc,CA8C1B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/auth-providers",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.44.0",
|
|
4
4
|
"description": "Authentication provider interfaces, base classes, and implementations for MemberJunction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"test:watch": "vitest"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@memberjunction/core": "5.
|
|
15
|
-
"@memberjunction/global": "5.
|
|
14
|
+
"@memberjunction/core": "5.44.0",
|
|
15
|
+
"@memberjunction/global": "5.44.0",
|
|
16
16
|
"graphql": "^16.12.0",
|
|
17
17
|
"jsonwebtoken": "9.0.3",
|
|
18
18
|
"jwks-rsa": "^3.2.2"
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for WorkOSProvider.
|
|
3
|
+
*
|
|
4
|
+
* Covers the two pieces of logic the provider owns on top of BaseAuthProvider:
|
|
5
|
+
* - extractUserInfo(): mapping WorkOS AuthKit JWT claims → AuthUserInfo, including the
|
|
6
|
+
* fallbacks that keep a usable identity when a custom JWT Template only sets `name`.
|
|
7
|
+
* - validateConfig(): the WorkOS-specific requirement (clientId) on top of the base fields.
|
|
8
|
+
*
|
|
9
|
+
* It also asserts the provider registers with the class factory under the `workos` key, which
|
|
10
|
+
* is what makes `AuthProviderFactory.createProvider({ type: 'workos', ... })` resolve it.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import type { JwtPayload } from 'jsonwebtoken';
|
|
14
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
15
|
+
import { AuthProviderConfig } from '@memberjunction/core';
|
|
16
|
+
import { BaseAuthProvider } from '../BaseAuthProvider';
|
|
17
|
+
import { WorkOSProvider } from '../providers/WorkOSProvider';
|
|
18
|
+
|
|
19
|
+
const CLIENT_ID = 'client_01HABCDEF';
|
|
20
|
+
|
|
21
|
+
function makeConfig(overrides: Partial<AuthProviderConfig> = {}): AuthProviderConfig {
|
|
22
|
+
return {
|
|
23
|
+
name: 'workos-test',
|
|
24
|
+
type: 'workos',
|
|
25
|
+
issuer: `https://api.workos.com/user_management/${CLIENT_ID}`,
|
|
26
|
+
audience: CLIENT_ID,
|
|
27
|
+
jwksUri: `https://api.workos.com/sso/jwks/${CLIENT_ID}`,
|
|
28
|
+
clientId: CLIENT_ID,
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeProvider(overrides: Partial<AuthProviderConfig> = {}): WorkOSProvider {
|
|
34
|
+
return new WorkOSProvider(makeConfig(overrides));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('WorkOSProvider', () => {
|
|
38
|
+
describe('extractUserInfo', () => {
|
|
39
|
+
it('maps a token shaped by the recommended JWT Template (email + given/family name)', () => {
|
|
40
|
+
const provider = makeProvider();
|
|
41
|
+
const payload: JwtPayload = {
|
|
42
|
+
sub: 'user_01HXYZ',
|
|
43
|
+
email: 'ada@example.com',
|
|
44
|
+
given_name: 'Ada',
|
|
45
|
+
family_name: 'Lovelace',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const info = provider.extractUserInfo(payload);
|
|
49
|
+
|
|
50
|
+
expect(info.email).toBe('ada@example.com');
|
|
51
|
+
expect(info.firstName).toBe('Ada');
|
|
52
|
+
expect(info.lastName).toBe('Lovelace');
|
|
53
|
+
// No preferred_username on WorkOS tokens — falls back to email.
|
|
54
|
+
expect(info.preferredUsername).toBe('ada@example.com');
|
|
55
|
+
// `sub` is surfaced as the provider user id.
|
|
56
|
+
expect(info.userId).toBe('user_01HXYZ');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('derives first/last name from a single `name` claim when given/family are absent', () => {
|
|
60
|
+
const provider = makeProvider();
|
|
61
|
+
const payload: JwtPayload = {
|
|
62
|
+
sub: 'user_01HXYZ',
|
|
63
|
+
email: 'grace.hopper@example.com',
|
|
64
|
+
name: 'Grace Hopper',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const info = provider.extractUserInfo(payload);
|
|
68
|
+
|
|
69
|
+
expect(info.fullName).toBe('Grace Hopper');
|
|
70
|
+
expect(info.firstName).toBe('Grace');
|
|
71
|
+
expect(info.lastName).toBe('Hopper');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('falls back to the single name token for both first and last when name has one word', () => {
|
|
75
|
+
const provider = makeProvider();
|
|
76
|
+
const payload: JwtPayload = { sub: 'user_1', email: 'cher@example.com', name: 'Cher' };
|
|
77
|
+
|
|
78
|
+
const info = provider.extractUserInfo(payload);
|
|
79
|
+
|
|
80
|
+
expect(info.firstName).toBe('Cher');
|
|
81
|
+
expect(info.lastName).toBe('Cher');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('prefers explicit given_name/family_name over the `name` split', () => {
|
|
85
|
+
const provider = makeProvider();
|
|
86
|
+
const payload: JwtPayload = {
|
|
87
|
+
sub: 'user_2',
|
|
88
|
+
email: 'jean.luc@example.com',
|
|
89
|
+
name: 'Wrong Split',
|
|
90
|
+
given_name: 'Jean-Luc',
|
|
91
|
+
family_name: 'Picard',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const info = provider.extractUserInfo(payload);
|
|
95
|
+
|
|
96
|
+
expect(info.firstName).toBe('Jean-Luc');
|
|
97
|
+
expect(info.lastName).toBe('Picard');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('honors an explicit preferred_username when present', () => {
|
|
101
|
+
const provider = makeProvider();
|
|
102
|
+
const payload: JwtPayload = {
|
|
103
|
+
sub: 'user_3',
|
|
104
|
+
email: 'alan@example.com',
|
|
105
|
+
preferred_username: 'aturing',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const info = provider.extractUserInfo(payload);
|
|
109
|
+
|
|
110
|
+
expect(info.preferredUsername).toBe('aturing');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('returns undefined email (rather than throwing) when no JWT Template added it', () => {
|
|
114
|
+
// This is the default WorkOS access token shape — identity/session claims only.
|
|
115
|
+
const provider = makeProvider();
|
|
116
|
+
const payload: JwtPayload = {
|
|
117
|
+
sub: 'user_4',
|
|
118
|
+
sid: 'session_01H',
|
|
119
|
+
org_id: 'org_01H',
|
|
120
|
+
role: 'admin',
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const info = provider.extractUserInfo(payload);
|
|
124
|
+
|
|
125
|
+
expect(info.email).toBeUndefined();
|
|
126
|
+
expect(info.userId).toBe('user_4');
|
|
127
|
+
expect(info.preferredUsername).toBeUndefined();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('validateConfig', () => {
|
|
132
|
+
it('is valid with the full WorkOS config (base fields + clientId)', () => {
|
|
133
|
+
expect(makeProvider().validateConfig()).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('is invalid without a clientId', () => {
|
|
137
|
+
expect(makeProvider({ clientId: undefined }).validateConfig()).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('is invalid when a base field (audience) is missing', () => {
|
|
141
|
+
expect(makeProvider({ audience: '' }).validateConfig()).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('exposes clientId on the instance for the OAuth proxy path', () => {
|
|
145
|
+
expect(makeProvider().clientId).toBe(CLIENT_ID);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('class-factory registration', () => {
|
|
150
|
+
it('is registered under the lowercase `workos` key and instantiable via the factory', () => {
|
|
151
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance<BaseAuthProvider>(
|
|
152
|
+
BaseAuthProvider,
|
|
153
|
+
'workos',
|
|
154
|
+
makeConfig(),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(instance).toBeInstanceOf(WorkOSProvider);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('issuer matching (inherited)', () => {
|
|
162
|
+
it('matches its configured issuer case-insensitively and ignores a trailing slash', () => {
|
|
163
|
+
const provider = makeProvider();
|
|
164
|
+
expect(provider.matchesIssuer(`https://api.workos.com/user_management/${CLIENT_ID}`)).toBe(true);
|
|
165
|
+
expect(provider.matchesIssuer(`https://api.workos.com/user_management/${CLIENT_ID}/`)).toBe(true);
|
|
166
|
+
expect(provider.matchesIssuer('https://api.workos.com/user_management/other')).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
3
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
4
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WorkOS (AuthKit) authentication provider implementation.
|
|
9
|
+
*
|
|
10
|
+
* Validates JWT access tokens minted by WorkOS AuthKit / User Management. These tokens are
|
|
11
|
+
* signed with RS256 and verified against the per-environment JWKS endpoint
|
|
12
|
+
* (`https://api.workos.com/sso/jwks/<clientId>`); their `iss` claim is
|
|
13
|
+
* `https://api.workos.com/user_management/<clientId>`.
|
|
14
|
+
*
|
|
15
|
+
* ## Email is required — and is NOT in a WorkOS token by default
|
|
16
|
+
* MemberJunction resolves the signed-in user by **email**, but a WorkOS AuthKit access token
|
|
17
|
+
* only carries identity/session claims (`sub`, `sid`, `org_id`, `role`, `permissions`) out of
|
|
18
|
+
* the box — it does **not** include `email`. To use WorkOS with MJ you must add the email (and
|
|
19
|
+
* ideally the name) to the token via a WorkOS **JWT Template**:
|
|
20
|
+
*
|
|
21
|
+
* ```json
|
|
22
|
+
* {
|
|
23
|
+
* "email": "{{user.email}}",
|
|
24
|
+
* "given_name": "{{user.first_name}}",
|
|
25
|
+
* "family_name": "{{user.last_name}}"
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* See `WORKOS.md` in this package for the full, step-by-step integration guide.
|
|
30
|
+
*/
|
|
31
|
+
@RegisterClass(BaseAuthProvider, 'workos')
|
|
32
|
+
export class WorkOSProvider extends BaseAuthProvider {
|
|
33
|
+
constructor(config: AuthProviderConfig) {
|
|
34
|
+
super(config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extracts user information from a WorkOS AuthKit JWT payload.
|
|
39
|
+
*
|
|
40
|
+
* `email`, `given_name`, and `family_name` are expected to be supplied by a WorkOS JWT
|
|
41
|
+
* Template (see class docs). `sub` is the stable WorkOS user id (`user_...`). We fall back
|
|
42
|
+
* through standard OIDC claim names so a token shaped by a custom template (e.g. one that
|
|
43
|
+
* only sets `name`) still resolves a usable identity.
|
|
44
|
+
*/
|
|
45
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo {
|
|
46
|
+
const email = payload.email as string | undefined;
|
|
47
|
+
const fullName = payload.name as string | undefined;
|
|
48
|
+
const firstName = payload.given_name as string | undefined;
|
|
49
|
+
const lastName = payload.family_name as string | undefined;
|
|
50
|
+
// WorkOS access tokens have no `preferred_username`; email is the natural handle.
|
|
51
|
+
const preferredUsername = payload.preferred_username as string | undefined || email;
|
|
52
|
+
// `sub` is the WorkOS user identifier (e.g. "user_01H...").
|
|
53
|
+
const userId = payload.sub as string | undefined;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
email,
|
|
57
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
58
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
59
|
+
fullName,
|
|
60
|
+
preferredUsername,
|
|
61
|
+
userId
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validates WorkOS-specific configuration.
|
|
67
|
+
*
|
|
68
|
+
* Beyond the base requirements (`name`, `issuer`, `audience`, `jwksUri`), WorkOS needs a
|
|
69
|
+
* `clientId` — it appears in both the issuer and JWKS URLs and identifies the AuthKit
|
|
70
|
+
* environment.
|
|
71
|
+
*/
|
|
72
|
+
validateConfig(): boolean {
|
|
73
|
+
const baseValid = super.validateConfig();
|
|
74
|
+
const hasClientId = !!this.config.clientId;
|
|
75
|
+
|
|
76
|
+
return baseValid && hasClientId;
|
|
77
|
+
}
|
|
78
|
+
}
|