@open-mercato/enterprise 0.4.6-develop-34aa847ce6 → 0.4.6-develop-7ffc0df6e5
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/package.json +4 -4
- package/src/modules/sso/docs/entra-id-setup.md +0 -281
- package/src/modules/sso/docs/google-workspace-setup.md +0 -174
- package/src/modules/sso/docs/sso-overview.md +0 -218
- package/src/modules/sso/docs/sso-security-audit-2026-02-27.md +0 -118
- package/src/modules/sso/docs/zitadel-setup.md +0 -195
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/enterprise",
|
|
3
|
-
"version": "0.4.6-develop-
|
|
3
|
+
"version": "0.4.6-develop-7ffc0df6e5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@open-mercato/core": "0.4.6-develop-
|
|
68
|
-
"@open-mercato/shared": "0.4.6-develop-
|
|
69
|
-
"@open-mercato/ui": "0.4.6-develop-
|
|
67
|
+
"@open-mercato/core": "0.4.6-develop-7ffc0df6e5",
|
|
68
|
+
"@open-mercato/shared": "0.4.6-develop-7ffc0df6e5",
|
|
69
|
+
"@open-mercato/ui": "0.4.6-develop-7ffc0df6e5",
|
|
70
70
|
"openid-client": "^6.3.3"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# Microsoft Entra ID Setup Guide for Open Mercato SSO + SCIM
|
|
2
|
-
|
|
3
|
-
This guide walks through setting up Microsoft Entra ID (formerly Azure AD) as the identity provider for both OIDC login and SCIM user provisioning in Open Mercato.
|
|
4
|
-
|
|
5
|
-
**Free tier**: Entra ID Free is included with any Azure subscription — no paid license required for basic OIDC + SCIM.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Create an Azure Account + Entra ID Tenant
|
|
10
|
-
|
|
11
|
-
1. Go to https://azure.microsoft.com/free and create a free account (or use an existing one)
|
|
12
|
-
2. Navigate to https://entra.microsoft.com (the Entra admin center)
|
|
13
|
-
3. You'll have a default tenant — note your **Tenant ID** from **Overview** → **Tenant ID**
|
|
14
|
-
|
|
15
|
-
## 2. Create Test Users
|
|
16
|
-
|
|
17
|
-
1. In the Entra admin center, go to **Identity** → **Users** → **All users**
|
|
18
|
-
2. Click **+ New user** → **Create new user**
|
|
19
|
-
3. Fill in:
|
|
20
|
-
- **User principal name**: e.g., `testuser@yourtenant.onmicrosoft.com`
|
|
21
|
-
- **Display name**: e.g., `Test User`
|
|
22
|
-
- **First name** / **Last name**
|
|
23
|
-
- **Password**: auto-generate or set manually
|
|
24
|
-
4. Click **Create**
|
|
25
|
-
5. Repeat for 2-3 test users
|
|
26
|
-
|
|
27
|
-
## 3. Register the OIDC Application (SSO Login)
|
|
28
|
-
|
|
29
|
-
1. In the Entra admin center, go to **Identity** → **Applications** → **App registrations**
|
|
30
|
-
2. Click **+ New registration**
|
|
31
|
-
3. Configure:
|
|
32
|
-
|
|
33
|
-
| Field | Value |
|
|
34
|
-
|-------|-------|
|
|
35
|
-
| **Name** | `Open Mercato` |
|
|
36
|
-
| **Supported account types** | `Accounts in this organizational directory only` (Single tenant) |
|
|
37
|
-
| **Redirect URI** | Platform: `Web`, URI: `http://localhost:3000/api/sso/callback/oidc` |
|
|
38
|
-
|
|
39
|
-
4. Click **Register**
|
|
40
|
-
5. You'll land on the app's **Overview** page — note:
|
|
41
|
-
- **Application (client) ID** — this is your Client ID
|
|
42
|
-
- **Directory (tenant) ID** — used in the issuer URL
|
|
43
|
-
|
|
44
|
-
### Create a Client Secret
|
|
45
|
-
|
|
46
|
-
1. Go to **Certificates & secrets** → **Client secrets** tab
|
|
47
|
-
2. Click **+ New client secret**
|
|
48
|
-
3. Description: `Open Mercato Dev`, Expiry: `6 months` (or your preference)
|
|
49
|
-
4. Click **Add**
|
|
50
|
-
5. **Copy the secret Value immediately** — it's shown only once
|
|
51
|
-
|
|
52
|
-
### OIDC Credentials Summary
|
|
53
|
-
|
|
54
|
-
| Credential | Where to find it | Value |
|
|
55
|
-
|------------|-----------------|-------|
|
|
56
|
-
| **Issuer URL** | Computed from Tenant ID | `https://login.microsoftonline.com/{tenant-id}/v2.0` |
|
|
57
|
-
| **Client ID** | App registration → Overview | Copy from portal |
|
|
58
|
-
| **Client Secret** | App registration → Certificates & secrets | Copy the **Value** (not Secret ID) |
|
|
59
|
-
| **Redirect URI** | You configured this | `http://localhost:3000/api/sso/callback/oidc` |
|
|
60
|
-
|
|
61
|
-
### Configure Token Claims
|
|
62
|
-
|
|
63
|
-
By default, Entra ID v2.0 tokens may not include `email` in the ID token. Fix this:
|
|
64
|
-
|
|
65
|
-
1. Go to your App registration → **Token configuration**
|
|
66
|
-
2. Click **+ Add optional claim**
|
|
67
|
-
3. Token type: **ID**
|
|
68
|
-
4. Check: `email`, `given_name`, `family_name`
|
|
69
|
-
5. Click **Add**
|
|
70
|
-
6. When prompted about Microsoft Graph permissions, check the box and click **Add**
|
|
71
|
-
|
|
72
|
-
### API Permissions
|
|
73
|
-
|
|
74
|
-
1. Go to **API permissions**
|
|
75
|
-
2. Verify these are present (they should be by default):
|
|
76
|
-
- `Microsoft Graph` → `openid` (Delegated)
|
|
77
|
-
- `Microsoft Graph` → `profile` (Delegated)
|
|
78
|
-
- `Microsoft Graph` → `email` (Delegated)
|
|
79
|
-
3. If any are missing, click **+ Add a permission** → **Microsoft Graph** → **Delegated permissions** → search and add them
|
|
80
|
-
4. Click **Grant admin consent for [your tenant]** (green checkmark button)
|
|
81
|
-
|
|
82
|
-
### Assign Users to the Application
|
|
83
|
-
|
|
84
|
-
1. Go to **Identity** → **Applications** → **Enterprise applications**
|
|
85
|
-
2. Find and click **Open Mercato**
|
|
86
|
-
3. Go to **Users and groups** → **+ Add user/group**
|
|
87
|
-
4. Select your test users (or a group containing them)
|
|
88
|
-
5. Click **Assign**
|
|
89
|
-
|
|
90
|
-
**Important**: If "Assignment required?" is set to **Yes** (under Properties), only assigned users can log in. Set to **No** for dev if you want all tenant users to access it.
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 4. Create the SSO Config in Open Mercato
|
|
95
|
-
|
|
96
|
-
1. Log into Open Mercato as admin
|
|
97
|
-
2. Go to **Settings** → **Single Sign-On** → **Create New**
|
|
98
|
-
3. Select **OIDC** as the protocol
|
|
99
|
-
4. Enter:
|
|
100
|
-
- **Name**: `Entra ID`
|
|
101
|
-
- **Issuer URL**: `https://login.microsoftonline.com/{your-tenant-id}/v2.0`
|
|
102
|
-
- **Client ID**: (paste from Entra)
|
|
103
|
-
- **Client Secret**: (paste the secret Value from Entra)
|
|
104
|
-
5. Add allowed email domains (e.g., `yourtenant.onmicrosoft.com`)
|
|
105
|
-
6. Test the connection (Verify Discovery)
|
|
106
|
-
7. Activate the config
|
|
107
|
-
|
|
108
|
-
### Verify OIDC Login
|
|
109
|
-
|
|
110
|
-
1. Open a private/incognito browser window
|
|
111
|
-
2. Go to the Open Mercato login page
|
|
112
|
-
3. Enter an email address belonging to one of your test users (e.g., `testuser@yourtenant.onmicrosoft.com`)
|
|
113
|
-
4. The HRD check should detect SSO and redirect to Microsoft login
|
|
114
|
-
5. Authenticate at Microsoft
|
|
115
|
-
6. You should be redirected back to Open Mercato and logged in
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 5. Configure SCIM Provisioning
|
|
120
|
-
|
|
121
|
-
**Prerequisite**: You need a SCIM bearer token from Open Mercato. Generate one via:
|
|
122
|
-
- The admin UI: SSO config → Provisioning tab → Generate Token
|
|
123
|
-
- Or the API: `POST /api/sso/scim/tokens` with the SSO config ID
|
|
124
|
-
|
|
125
|
-
### Set Up Provisioning in Entra ID
|
|
126
|
-
|
|
127
|
-
1. Go to **Identity** → **Applications** → **Enterprise applications**
|
|
128
|
-
2. Find and click **Open Mercato**
|
|
129
|
-
3. Go to **Provisioning** → click **Get started**
|
|
130
|
-
4. Set **Provisioning Mode** to **Automatic**
|
|
131
|
-
5. In **Admin Credentials**:
|
|
132
|
-
|
|
133
|
-
| Field | Value |
|
|
134
|
-
|-------|-------|
|
|
135
|
-
| **Tenant URL** | `http://localhost:3000/api/sso/scim/v2` (dev) or `https://<your-domain>/api/sso/scim/v2` (prod) |
|
|
136
|
-
| **Secret Token** | Paste the SCIM bearer token from Open Mercato |
|
|
137
|
-
|
|
138
|
-
6. Click **Test Connection** — should show "The supplied credentials are authorized to enable provisioning"
|
|
139
|
-
7. Click **Save**
|
|
140
|
-
|
|
141
|
-
### Configure Attribute Mappings
|
|
142
|
-
|
|
143
|
-
1. Under **Mappings**, click **Provision Microsoft Entra ID Users**
|
|
144
|
-
2. Verify these mappings exist:
|
|
145
|
-
|
|
146
|
-
| Entra ID Attribute | SCIM Attribute | Notes |
|
|
147
|
-
|--------------------|----------------|-------|
|
|
148
|
-
| `userPrincipalName` | `userName` | Required |
|
|
149
|
-
| `Switch([IsSoftDeleted]...)` | `active` | Required — Entra uses a Switch expression |
|
|
150
|
-
| `givenName` | `name.givenName` | Required |
|
|
151
|
-
| `surname` | `name.familyName` | Required |
|
|
152
|
-
| `mail` | `emails[type eq "work"].value` | Required — user's email |
|
|
153
|
-
| `displayName` | `displayName` | Optional |
|
|
154
|
-
| `objectId` | `externalId` | Required — Entra's unique ID |
|
|
155
|
-
|
|
156
|
-
3. Keep default mappings — they should work out of the box
|
|
157
|
-
4. Click **Save**
|
|
158
|
-
|
|
159
|
-
### Start Provisioning
|
|
160
|
-
|
|
161
|
-
1. Back on the **Provisioning** page, set **Provisioning Status** to **On**
|
|
162
|
-
2. Click **Save**
|
|
163
|
-
3. Entra will run an initial provisioning cycle (may take up to 40 minutes for the first cycle)
|
|
164
|
-
4. Check **Provisioning logs** for results
|
|
165
|
-
|
|
166
|
-
### Provisioning Cycle Timing
|
|
167
|
-
|
|
168
|
-
- **Initial cycle**: Processes all users in scope. Can take 20-40 minutes.
|
|
169
|
-
- **Incremental cycles**: Every 40 minutes, processes changes since last cycle.
|
|
170
|
-
- **On-demand provisioning**: Click **Provision on demand** to immediately provision a specific user (useful for testing).
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## 6. Test the Full Flow
|
|
175
|
-
|
|
176
|
-
### Test SCIM Provisioning
|
|
177
|
-
|
|
178
|
-
1. In Entra, go to **Enterprise applications** → **Open Mercato** → **Provisioning**
|
|
179
|
-
2. Click **Provision on demand**
|
|
180
|
-
3. Search for a test user and click **Provision**
|
|
181
|
-
4. **Expected**: Entra sends `POST /Users` to your SCIM endpoint → user appears in Open Mercato
|
|
182
|
-
5. Check the provisioning log in Open Mercato admin UI
|
|
183
|
-
|
|
184
|
-
### Test User Update
|
|
185
|
-
|
|
186
|
-
1. In Entra, go to **Users** → edit a test user's display name
|
|
187
|
-
2. Wait for the next provisioning cycle (or use Provision on demand)
|
|
188
|
-
3. **Expected**: Entra sends `PATCH /Users/{id}` → user's name updated in Open Mercato
|
|
189
|
-
|
|
190
|
-
### Test User Deactivation
|
|
191
|
-
|
|
192
|
-
1. In Entra, either:
|
|
193
|
-
- **Delete** the user (soft-delete moves to Deleted users)
|
|
194
|
-
- **Block sign-in** for the user (Users → select user → Edit properties → Block sign in: Yes)
|
|
195
|
-
- **Remove** the user from the application assignment
|
|
196
|
-
2. **Expected**: Entra sends `PATCH /Users/{id}` with `active: false` → user deactivated in Open Mercato, all sessions revoked
|
|
197
|
-
|
|
198
|
-
### Test OIDC + SCIM Together
|
|
199
|
-
|
|
200
|
-
1. **Create a new user in Entra** and assign them to the Open Mercato Enterprise app
|
|
201
|
-
2. **Provision on demand** (or wait for cycle)
|
|
202
|
-
3. **Verify** the user exists in Open Mercato (pre-provisioned, no login needed)
|
|
203
|
-
4. **Log in as that user** via OIDC (Open Mercato login → redirect to Microsoft → authenticate → redirect back)
|
|
204
|
-
5. **Expected**: The SCIM-provisioned account is used (no JIT provisioning, `provisioningMethod` stays `scim`)
|
|
205
|
-
6. **Block sign-in for the user in Entra**
|
|
206
|
-
7. **Expected**: SCIM deactivates the user → existing sessions revoked → OIDC login no longer works
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Entra ID SCIM Quirks
|
|
211
|
-
|
|
212
|
-
When building the SCIM endpoint, account for these Entra-specific behaviors:
|
|
213
|
-
|
|
214
|
-
| Quirk | Description | How to handle |
|
|
215
|
-
|-------|-------------|---------------|
|
|
216
|
-
| **PascalCase `op` in PATCH** | Entra sends `"op": "Replace"` instead of `"op": "replace"` | Case-insensitive comparison on PATCH operations |
|
|
217
|
-
| **String booleans** | `active` may be sent as `"True"` / `"False"` strings | Parse with `parseBooleanToken` |
|
|
218
|
-
| **Non-standard PATCH paths** | Sometimes sends `emails[type eq "work"].value` in PATCH path | Support bracket-notation in PATCH path parser |
|
|
219
|
-
| **Mixed-case filter operators** | Sends `Eq` instead of `eq` in filters | Case-insensitive filter parsing |
|
|
220
|
-
| **`externalId` mapping** | Maps `objectId` → `externalId` by default | Always store `externalId` from SCIM requests |
|
|
221
|
-
| **Soft delete** | Uses `IsSoftDeleted` Switch expression → `active: false` | Handle as user deactivation |
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## Troubleshooting
|
|
226
|
-
|
|
227
|
-
### OIDC login redirects but fails
|
|
228
|
-
|
|
229
|
-
- Verify the Redirect URI in App Registration matches exactly: `http://localhost:3000/api/sso/callback/oidc`
|
|
230
|
-
- Check that the Issuer URL includes the tenant ID: `https://login.microsoftonline.com/{tenant-id}/v2.0`
|
|
231
|
-
- Verify Client ID and Client Secret (the Value, not the Secret ID)
|
|
232
|
-
- Ensure `email` optional claim is added to the ID token
|
|
233
|
-
- Ensure API permissions have admin consent granted
|
|
234
|
-
|
|
235
|
-
### "AADSTS50011: The redirect URI does not match"
|
|
236
|
-
|
|
237
|
-
The redirect URI in the authorization request doesn't match what's registered. Check:
|
|
238
|
-
- `APP_URL` in `.env` matches what you registered (e.g., `http://localhost:3000`)
|
|
239
|
-
- No trailing slash differences
|
|
240
|
-
- Protocol matches (http vs https)
|
|
241
|
-
|
|
242
|
-
### Users not provisioning
|
|
243
|
-
|
|
244
|
-
- Check that users are assigned to the Enterprise application
|
|
245
|
-
- Check **Provisioning logs** in Entra for error details
|
|
246
|
-
- Verify the SCIM token is valid and not revoked
|
|
247
|
-
- For local dev, Entra needs to reach your server — use ngrok for SCIM (even though OIDC works with localhost)
|
|
248
|
-
|
|
249
|
-
### SCIM "Test Connection" fails
|
|
250
|
-
|
|
251
|
-
- For local dev, Entra's provisioning service needs to reach your endpoint over the internet
|
|
252
|
-
- Use ngrok: `ngrok http 3000`
|
|
253
|
-
- Set Tenant URL to: `https://<id>.ngrok-free.app/api/sso/scim/v2`
|
|
254
|
-
- **Note**: OIDC redirect URIs can use `localhost`, but SCIM provisioning requires a publicly reachable URL
|
|
255
|
-
|
|
256
|
-
### email claim missing from ID token
|
|
257
|
-
|
|
258
|
-
1. Go to App registration → **Token configuration** → **+ Add optional claim** → ID token → check `email`
|
|
259
|
-
2. Go to **API permissions** → verify `email` permission → click **Grant admin consent**
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## Key Differences from JumpCloud
|
|
264
|
-
|
|
265
|
-
| Aspect | Entra ID | JumpCloud |
|
|
266
|
-
|--------|----------|-----------|
|
|
267
|
-
| **Issuer URL** | `https://login.microsoftonline.com/{tenant-id}/v2.0` | `https://oauth.id.jumpcloud.com/` |
|
|
268
|
-
| **Redirect URI** | Supports `http://localhost` for dev | Requires HTTPS |
|
|
269
|
-
| **SCIM provisioning** | Enterprise App → Provisioning (automatic) | SSO App → Identity Management (SCIM API) |
|
|
270
|
-
| **Provisioning cycles** | Every 40 minutes (or on-demand) | Near real-time |
|
|
271
|
-
| **SCIM quirks** | PascalCase ops, string booleans, mixed-case filters | Mostly spec-compliant |
|
|
272
|
-
| **Free tier** | Free with any Azure account | 10 users forever |
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
## Reference
|
|
277
|
-
|
|
278
|
-
- [Entra ID App Registration - OIDC](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
|
|
279
|
-
- [Entra ID SCIM Provisioning](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups)
|
|
280
|
-
- [Entra ID Optional Claims](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims)
|
|
281
|
-
- [Entra ID SCIM Known Issues](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/application-provisioning-config-problem-scim-compatibility)
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# Google Workspace Setup Guide for Open Mercato SSO
|
|
2
|
-
|
|
3
|
-
This guide walks through setting up Google Workspace as the identity provider for OIDC login in Open Mercato. Google Workspace supports JIT (Just-In-Time) provisioning only — SCIM push provisioning is not available.
|
|
4
|
-
|
|
5
|
-
**Free tier**: Google Cloud OAuth 2.0 is free for internal Workspace applications. No paid APIs required.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Prerequisites
|
|
10
|
-
|
|
11
|
-
- A Google Workspace account with admin access
|
|
12
|
-
- A custom domain verified in Google Workspace (e.g., `company.com`)
|
|
13
|
-
- Access to the Google Cloud Console (https://console.cloud.google.com)
|
|
14
|
-
|
|
15
|
-
## 2. Create a Google Cloud Project
|
|
16
|
-
|
|
17
|
-
1. Go to https://console.cloud.google.com
|
|
18
|
-
2. Click the project selector in the top bar → **New Project**
|
|
19
|
-
3. Name: `Open Mercato SSO` (or your preference)
|
|
20
|
-
4. Click **Create**
|
|
21
|
-
5. Switch to the new project in the project selector
|
|
22
|
-
|
|
23
|
-
## 3. Configure the OAuth Consent Screen
|
|
24
|
-
|
|
25
|
-
1. In the left sidebar, go to **APIs & Services** → **OAuth consent screen**
|
|
26
|
-
2. Select **Internal** (restricts login to your Workspace organization only)
|
|
27
|
-
3. Click **Create**
|
|
28
|
-
4. Fill in:
|
|
29
|
-
|
|
30
|
-
| Field | Value |
|
|
31
|
-
|-------|-------|
|
|
32
|
-
| **App name** | `Open Mercato` |
|
|
33
|
-
| **User support email** | Your admin email |
|
|
34
|
-
| **Authorized domains** | Your Workspace domain (e.g., `company.com`) |
|
|
35
|
-
| **Developer contact email** | Your admin email |
|
|
36
|
-
|
|
37
|
-
5. Click **Save and Continue**
|
|
38
|
-
6. On the **Scopes** step, click **Add or Remove Scopes** and add:
|
|
39
|
-
- `openid`
|
|
40
|
-
- `email`
|
|
41
|
-
- `profile`
|
|
42
|
-
7. Click **Update** → **Save and Continue**
|
|
43
|
-
8. Review and click **Back to Dashboard**
|
|
44
|
-
|
|
45
|
-
## 4. Create OAuth 2.0 Credentials
|
|
46
|
-
|
|
47
|
-
1. Go to **APIs & Services** → **Credentials**
|
|
48
|
-
2. Click **+ Create Credentials** → **OAuth client ID**
|
|
49
|
-
3. Configure:
|
|
50
|
-
|
|
51
|
-
| Field | Value |
|
|
52
|
-
|-------|-------|
|
|
53
|
-
| **Application type** | `Web application` |
|
|
54
|
-
| **Name** | `Open Mercato SSO` |
|
|
55
|
-
| **Authorized redirect URIs** | `http://localhost:3000/api/sso/callback/oidc` |
|
|
56
|
-
|
|
57
|
-
4. Click **Create**
|
|
58
|
-
5. **Copy the Client ID and Client Secret immediately** — you can also retrieve them later from the credentials list
|
|
59
|
-
|
|
60
|
-
### OIDC Credentials Summary
|
|
61
|
-
|
|
62
|
-
| Credential | Value |
|
|
63
|
-
|------------|-------|
|
|
64
|
-
| **Issuer URL** | `https://accounts.google.com` |
|
|
65
|
-
| **Client ID** | Copy from Credentials page |
|
|
66
|
-
| **Client Secret** | Copy from Credentials page |
|
|
67
|
-
| **Redirect URI** | `http://localhost:3000/api/sso/callback/oidc` |
|
|
68
|
-
|
|
69
|
-
**Note**: Google's OIDC discovery document is at `https://accounts.google.com/.well-known/openid-configuration`.
|
|
70
|
-
|
|
71
|
-
## 5. Create the SSO Config in Open Mercato
|
|
72
|
-
|
|
73
|
-
1. Log into Open Mercato as admin
|
|
74
|
-
2. Go to **Settings** → **Single Sign-On** → **Create New**
|
|
75
|
-
3. Select **OIDC** as the protocol
|
|
76
|
-
4. Enter:
|
|
77
|
-
- **Name**: `Google Workspace`
|
|
78
|
-
- **Issuer URL**: `https://accounts.google.com`
|
|
79
|
-
- **Client ID**: (paste from Google Cloud Console)
|
|
80
|
-
- **Client Secret**: (paste from Google Cloud Console)
|
|
81
|
-
5. Add your Workspace domain as an allowed domain (e.g., `company.com`)
|
|
82
|
-
6. Enable **JIT Provisioning** (recommended — creates accounts on first login)
|
|
83
|
-
7. Enable **Auto-link by email** (recommended — links existing accounts by email match)
|
|
84
|
-
8. Click **Verify Discovery** to test the OIDC configuration
|
|
85
|
-
9. Save and activate the configuration
|
|
86
|
-
|
|
87
|
-
## 6. Verify OIDC Login
|
|
88
|
-
|
|
89
|
-
1. Open a private/incognito browser window
|
|
90
|
-
2. Go to the Open Mercato login page
|
|
91
|
-
3. Enter an email address with your Workspace domain (e.g., `user@company.com`)
|
|
92
|
-
4. The login page should detect SSO and show "Continue with SSO"
|
|
93
|
-
5. Click it — you'll be redirected to Google's login page
|
|
94
|
-
6. Authenticate with your Google Workspace account
|
|
95
|
-
7. You should be redirected back to Open Mercato and logged in
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Google Workspace Specifics
|
|
100
|
-
|
|
101
|
-
### No SCIM Provisioning
|
|
102
|
-
|
|
103
|
-
Google Workspace does not support SCIM push provisioning to third-party applications. Users are provisioned via JIT on their first SSO login. The Provisioning tab in the Open Mercato admin UI will show an informational message for Google Workspace configurations.
|
|
104
|
-
|
|
105
|
-
To manage user access:
|
|
106
|
-
- **Provision**: Users are created automatically on first login via JIT
|
|
107
|
-
- **Deprovision**: Remove the user's Workspace account or change their domain to stop SSO access
|
|
108
|
-
|
|
109
|
-
### No Group Claims by Default
|
|
110
|
-
|
|
111
|
-
Google's standard OIDC tokens do not include group membership claims. If you need role-based access from Google groups, you would need to configure a custom claim via Google's Directory API (advanced setup, not covered here).
|
|
112
|
-
|
|
113
|
-
For most setups: leave **Role Mappings** empty in the SSO config. Users will log in with their default assigned role.
|
|
114
|
-
|
|
115
|
-
### `hd` Claim
|
|
116
|
-
|
|
117
|
-
Google OIDC returns a `hd` (hosted domain) claim for Workspace accounts. This identifies the user's organization domain. Open Mercato validates the user's email domain against the allowed domains configured in the SSO config.
|
|
118
|
-
|
|
119
|
-
### `email_verified`
|
|
120
|
-
|
|
121
|
-
Google Workspace accounts always return `email_verified: true`. Personal Gmail accounts may have unverified emails — configuring the consent screen as **Internal** prevents personal accounts from accessing the application.
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Troubleshooting
|
|
126
|
-
|
|
127
|
-
### "Access blocked: Open Mercato has not completed the Google verification process"
|
|
128
|
-
|
|
129
|
-
This appears when the consent screen is set to **External** without Google verification. Solution: set the consent screen to **Internal** (Workspace users only).
|
|
130
|
-
|
|
131
|
-
### OIDC login redirects but fails
|
|
132
|
-
|
|
133
|
-
- Verify the Redirect URI matches exactly: `http://localhost:3000/api/sso/callback/oidc`
|
|
134
|
-
- Verify the Issuer URL is `https://accounts.google.com` (not a tenant-specific URL)
|
|
135
|
-
- Ensure `openid`, `email`, and `profile` scopes are configured on the consent screen
|
|
136
|
-
- Check that the user's email domain matches an allowed domain in the SSO config
|
|
137
|
-
|
|
138
|
-
### "Error 400: redirect_uri_mismatch"
|
|
139
|
-
|
|
140
|
-
The redirect URI in the authorization request doesn't match what's registered in Google Cloud Console. Check:
|
|
141
|
-
- `APP_URL` in `.env` matches what you registered (e.g., `http://localhost:3000`)
|
|
142
|
-
- No trailing slash differences
|
|
143
|
-
- Protocol matches (http vs https)
|
|
144
|
-
- The URI is listed in **Authorized redirect URIs**, not **Authorized JavaScript origins**
|
|
145
|
-
|
|
146
|
-
### User gets "No roles could be resolved"
|
|
147
|
-
|
|
148
|
-
This happens when **Role Mappings** are configured but Google doesn't send group claims. Solution: clear the Role Mappings section in the SSO config (leave it empty) to allow login without IdP-based role assignment.
|
|
149
|
-
|
|
150
|
-
### Personal Gmail accounts can log in
|
|
151
|
-
|
|
152
|
-
If you set the consent screen to **External**, any Google account can authenticate. To restrict to your organization only, set the consent screen to **Internal**.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Key Differences from Entra ID
|
|
157
|
-
|
|
158
|
-
| Aspect | Google Workspace | Entra ID |
|
|
159
|
-
|--------|-----------------|----------|
|
|
160
|
-
| **Issuer URL** | `https://accounts.google.com` (same for all orgs) | `https://login.microsoftonline.com/{tenant-id}/v2.0` |
|
|
161
|
-
| **SCIM provisioning** | Not supported | Enterprise App → Provisioning |
|
|
162
|
-
| **Group claims** | Not in standard OIDC tokens | Via optional claims configuration |
|
|
163
|
-
| **User provisioning** | JIT only (on first login) | JIT or SCIM (automatic sync) |
|
|
164
|
-
| **Org restriction** | OAuth consent screen: Internal | App registration: Single tenant |
|
|
165
|
-
| **Free tier** | Free with any Workspace plan | Free with any Azure account |
|
|
166
|
-
| **Redirect URIs** | Supports `http://localhost` for dev | Supports `http://localhost` for dev |
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## Reference
|
|
171
|
-
|
|
172
|
-
- [Google Cloud OAuth 2.0 Setup](https://developers.google.com/identity/protocols/oauth2)
|
|
173
|
-
- [Google OpenID Connect](https://developers.google.com/identity/openid-connect/openid-connect)
|
|
174
|
-
- [Google Workspace Admin: OAuth Apps](https://support.google.com/a/answer/7281227)
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
# SSO Module Overview
|
|
2
|
-
|
|
3
|
-
Open Mercato's SSO module provides enterprise-grade Single Sign-On with OIDC and SCIM 2.0 support. This document covers architecture, configuration, and operational guidance.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Supported Identity Providers
|
|
8
|
-
|
|
9
|
-
| IdP | OIDC Login | SCIM Provisioning | JIT Provisioning | Notes |
|
|
10
|
-
|-----|------------|-------------------|------------------|-------|
|
|
11
|
-
| **Microsoft Entra ID** | Yes | Yes | Yes | Full OIDC + SCIM support. See [Entra ID Setup Guide](./entra-id-setup.md) |
|
|
12
|
-
| **Zitadel** | Yes | Yes | Yes | OIDC + SCIM via Actions/native. See [Zitadel Setup Guide](./zitadel-setup.md) |
|
|
13
|
-
| **Google Workspace** | Yes | No | Yes (recommended) | OIDC only, no SCIM push. See [Google Workspace Setup Guide](./google-workspace-setup.md) |
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Architecture
|
|
18
|
-
|
|
19
|
-
### Authentication Flow (OIDC)
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
User → Login Page → HRD Check → IdP Redirect → IdP Login → Callback → Session
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
1. **Home Realm Discovery (HRD)**: User enters email, the system checks if the email domain matches an active SSO config
|
|
26
|
-
2. **Authorization Request**: OIDC Authorization Code + PKCE flow initiated with encrypted state cookie
|
|
27
|
-
3. **IdP Authentication**: User authenticates at the identity provider
|
|
28
|
-
4. **Callback Processing**: Authorization code exchanged for tokens, ID token validated
|
|
29
|
-
5. **Account Linking**: User matched to existing account (by email or SSO subject) or JIT-provisioned
|
|
30
|
-
6. **Session Creation**: Auth session established, user redirected to the application
|
|
31
|
-
|
|
32
|
-
### User Provisioning
|
|
33
|
-
|
|
34
|
-
Two provisioning methods are supported, **mutually exclusive** per SSO config:
|
|
35
|
-
|
|
36
|
-
**JIT (Just-In-Time) Provisioning**
|
|
37
|
-
- Users are created automatically on first OIDC login
|
|
38
|
-
- Profile data extracted from ID token claims
|
|
39
|
-
- Best for: Google Workspace, small organizations, simple setups
|
|
40
|
-
|
|
41
|
-
**SCIM 2.0 Provisioning**
|
|
42
|
-
- Users are pre-provisioned by the IdP before first login
|
|
43
|
-
- Supports create, update, deactivate, and delete operations
|
|
44
|
-
- Best for: Entra ID, large organizations needing lifecycle management
|
|
45
|
-
|
|
46
|
-
### Mutual Exclusivity
|
|
47
|
-
|
|
48
|
-
JIT and SCIM cannot be enabled simultaneously on the same SSO config:
|
|
49
|
-
- Enabling JIT blocks SCIM token creation
|
|
50
|
-
- Creating SCIM tokens blocks enabling JIT
|
|
51
|
-
- Switching requires disabling one before enabling the other
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Configuration
|
|
56
|
-
|
|
57
|
-
### Admin Setup Steps
|
|
58
|
-
|
|
59
|
-
1. **Create SSO Config**: Settings → Single Sign-On → Create New
|
|
60
|
-
2. **Enter IdP Credentials**: Issuer URL, Client ID, Client Secret
|
|
61
|
-
3. **Add Allowed Domains**: Email domains that should use this SSO config
|
|
62
|
-
4. **Choose Provisioning**: Enable JIT or configure SCIM tokens
|
|
63
|
-
5. **Test Connection**: Verify the IdP discovery endpoint is reachable
|
|
64
|
-
6. **Activate**: Enable the config for production use
|
|
65
|
-
|
|
66
|
-
### API Endpoints
|
|
67
|
-
|
|
68
|
-
| Endpoint | Method | Description |
|
|
69
|
-
|----------|--------|-------------|
|
|
70
|
-
| `/api/sso/config` | POST | Create SSO config |
|
|
71
|
-
| `/api/sso/config` | GET | List SSO configs |
|
|
72
|
-
| `/api/sso/config/:id` | GET | Get config by ID |
|
|
73
|
-
| `/api/sso/config/:id` | PUT | Update config |
|
|
74
|
-
| `/api/sso/config/:id` | DELETE | Delete config (must be inactive) |
|
|
75
|
-
| `/api/sso/config/:id/activate` | POST | Activate/deactivate config |
|
|
76
|
-
| `/api/sso/config/:id/domains` | POST | Add domain |
|
|
77
|
-
| `/api/sso/config/:id/domains` | DELETE | Remove domain |
|
|
78
|
-
| `/api/sso/config/:id/test` | POST | Test IdP connection |
|
|
79
|
-
| `/api/sso/hrd` | POST | Home Realm Discovery lookup |
|
|
80
|
-
| `/api/sso/initiate` | GET | Start SSO login flow |
|
|
81
|
-
| `/api/sso/callback/oidc` | GET | OIDC callback |
|
|
82
|
-
| `/api/sso/scim/tokens` | POST | Create SCIM token |
|
|
83
|
-
| `/api/sso/scim/tokens` | GET | List SCIM tokens |
|
|
84
|
-
| `/api/sso/scim/tokens/:id` | DELETE | Revoke SCIM token |
|
|
85
|
-
| `/api/sso/scim/v2/Users` | POST | SCIM: Create user |
|
|
86
|
-
| `/api/sso/scim/v2/Users` | GET | SCIM: List users |
|
|
87
|
-
| `/api/sso/scim/v2/Users/:id` | GET | SCIM: Get user |
|
|
88
|
-
| `/api/sso/scim/v2/Users/:id` | PATCH | SCIM: Update user |
|
|
89
|
-
| `/api/sso/scim/v2/Users/:id` | DELETE | SCIM: Delete user |
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## Security
|
|
94
|
-
|
|
95
|
-
### OIDC Security Controls
|
|
96
|
-
|
|
97
|
-
| Control | Implementation |
|
|
98
|
-
|---------|---------------|
|
|
99
|
-
| **PKCE** | S256 with 32-byte random code verifier |
|
|
100
|
-
| **State Parameter** | AES-256-GCM encrypted state cookie with HKDF key derivation |
|
|
101
|
-
| **Nonce** | 16-byte random nonce validated in ID token |
|
|
102
|
-
| **State Comparison** | Timing-safe (`crypto.timingSafeEqual`) |
|
|
103
|
-
| **TTL** | 5-minute state cookie lifetime |
|
|
104
|
-
| **CSRF** | SameSite=Lax cookies + encrypted state parameter |
|
|
105
|
-
| **Return URL** | Sanitized to prevent open redirects |
|
|
106
|
-
|
|
107
|
-
### SCIM Security Controls
|
|
108
|
-
|
|
109
|
-
| Control | Implementation |
|
|
110
|
-
|---------|---------------|
|
|
111
|
-
| **Token Format** | `omscim_` prefix + 32 random bytes (hex) |
|
|
112
|
-
| **Storage** | bcrypt-hashed (cost 10), only prefix stored |
|
|
113
|
-
| **One-Time Display** | Raw token returned only at creation |
|
|
114
|
-
| **Timing Attack** | Dummy bcrypt hash on zero candidates |
|
|
115
|
-
| **Tenant Isolation** | Organization ID derived from token, not request |
|
|
116
|
-
|
|
117
|
-
### Data Protection
|
|
118
|
-
|
|
119
|
-
- OIDC client secrets encrypted at rest (AES via `TenantDataEncryptionService`)
|
|
120
|
-
- SCIM tokens bcrypt-hashed, never retrievable after creation
|
|
121
|
-
- No PII in server logs
|
|
122
|
-
- All admin endpoints require authentication + feature-based RBAC
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Provisioning Methods
|
|
127
|
-
|
|
128
|
-
### JIT Provisioning
|
|
129
|
-
|
|
130
|
-
When JIT is enabled on an SSO config:
|
|
131
|
-
|
|
132
|
-
1. User authenticates via OIDC at the IdP
|
|
133
|
-
2. If the user does not exist in Open Mercato, a new account is created
|
|
134
|
-
3. Profile data (name, email) extracted from ID token claims
|
|
135
|
-
4. User is assigned to the organization associated with the SSO config
|
|
136
|
-
5. On subsequent logins, profile data is updated from the latest ID token
|
|
137
|
-
|
|
138
|
-
**Limitations**:
|
|
139
|
-
- User lifecycle not managed (no automatic deactivation)
|
|
140
|
-
- No pre-provisioning (user must log in first)
|
|
141
|
-
- Role assignment requires manual configuration or IdP group claims
|
|
142
|
-
|
|
143
|
-
### SCIM 2.0 Provisioning
|
|
144
|
-
|
|
145
|
-
When SCIM is configured:
|
|
146
|
-
|
|
147
|
-
1. IdP pushes user create/update/delete operations to the SCIM endpoint
|
|
148
|
-
2. Users are pre-provisioned before their first login
|
|
149
|
-
3. Profile changes in the IdP are automatically synced
|
|
150
|
-
4. User deactivation in the IdP triggers deactivation + session revocation
|
|
151
|
-
5. On OIDC login, the existing SCIM-provisioned account is linked (no duplicate)
|
|
152
|
-
|
|
153
|
-
**Supported SCIM Operations**:
|
|
154
|
-
- `POST /Users` — Create user
|
|
155
|
-
- `GET /Users` — List users (with `eq` filter support)
|
|
156
|
-
- `GET /Users/:id` — Get user
|
|
157
|
-
- `PATCH /Users/:id` — Update user (replace operations on `displayName`, `active`, `name.*`, `emails`)
|
|
158
|
-
- `DELETE /Users/:id` — Delete user (soft-delete + deactivation)
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## Role Mapping
|
|
163
|
-
|
|
164
|
-
SSO configs support IdP group-to-application role mapping:
|
|
165
|
-
|
|
166
|
-
1. Configure `appRoleMappings` on the SSO config (map IdP group names to app role names)
|
|
167
|
-
2. When the IdP sends group claims in the ID token, roles are automatically assigned
|
|
168
|
-
3. If no mappings are configured, role sync is skipped (user retains existing roles)
|
|
169
|
-
|
|
170
|
-
**Google Workspace note**: Google does not send group claims by default. Role mapping is not available for Google OIDC without additional configuration.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Environment Variables
|
|
175
|
-
|
|
176
|
-
| Variable | Required | Description |
|
|
177
|
-
|----------|----------|-------------|
|
|
178
|
-
| `SSO_STATE_SECRET` | Yes (production) | 32+ byte secret for state cookie encryption |
|
|
179
|
-
| `APP_URL` / `NEXT_PUBLIC_APP_URL` | Recommended | Base URL for redirect URI construction |
|
|
180
|
-
| `SSO_DEV_SEED` | No | Set to `true` to seed demo SSO config in development |
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## Troubleshooting
|
|
185
|
-
|
|
186
|
-
### Common Issues
|
|
187
|
-
|
|
188
|
-
**"State mismatch — possible CSRF attack"**
|
|
189
|
-
- State cookie expired (5-minute TTL). User took too long at the IdP.
|
|
190
|
-
- Browser blocking third-party cookies. Ensure SameSite=Lax cookies are allowed.
|
|
191
|
-
|
|
192
|
-
**"No roles could be resolved from IdP groups"**
|
|
193
|
-
- Role mappings are configured but the IdP isn't sending matching group claims.
|
|
194
|
-
- Remove role mappings if not needed, or configure the IdP to send group claims.
|
|
195
|
-
|
|
196
|
-
**User created with wrong provisioning method**
|
|
197
|
-
- Check if both JIT and SCIM have been toggled. The system enforces mutual exclusivity.
|
|
198
|
-
- Verify the `provisioningMethod` field on the user's SSO link record.
|
|
199
|
-
|
|
200
|
-
**SCIM requests return 401**
|
|
201
|
-
- Token may be revoked. Check token status in the admin UI.
|
|
202
|
-
- Token format: must include `Authorization: Bearer omscim_...` header.
|
|
203
|
-
- Check that the SSO config is active.
|
|
204
|
-
|
|
205
|
-
**SCIM requests return 403**
|
|
206
|
-
- The SSO config associated with the token is inactive. Activate it first.
|
|
207
|
-
|
|
208
|
-
**HRD not detecting SSO for an email domain**
|
|
209
|
-
- Verify the domain is added to the SSO config's allowed domains.
|
|
210
|
-
- Verify the SSO config is activated (inactive configs are not returned by HRD).
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## IdP-Specific Setup Guides
|
|
215
|
-
|
|
216
|
-
- [Microsoft Entra ID Setup Guide](./entra-id-setup.md) — Full OIDC + SCIM setup
|
|
217
|
-
- [Zitadel Setup Guide](./zitadel-setup.md) — OIDC + SCIM setup
|
|
218
|
-
- [Google Workspace Setup Guide](./google-workspace-setup.md) — OIDC-only setup (JIT recommended)
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
# SSO Module Security Audit Report
|
|
2
|
-
|
|
3
|
-
**Module:** `packages/enterprise/src/modules/sso/`
|
|
4
|
-
**Date:** 2026-02-27
|
|
5
|
-
**Branch:** `feat/sso-support`
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Executive Summary
|
|
10
|
-
|
|
11
|
-
The SSO module demonstrates strong security fundamentals: AES-256-GCM encrypted state cookies, PKCE enforcement, timing-safe state comparison, bcrypt-hashed SCIM tokens, and encrypted OIDC client secrets. The audit uncovered **2 High**, **3 Medium**, and **4 Low** severity findings. Both HIGH findings have been remediated in this milestone.
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Findings Summary
|
|
16
|
-
|
|
17
|
-
| # | Finding | Severity | Status |
|
|
18
|
-
|---|---------|----------|--------|
|
|
19
|
-
| F1 | SCIM debug `console.log` statements dump PII to logs | **HIGH** | **FIXED** |
|
|
20
|
-
| F2 | Missing org ownership check in SCIM token generation | **HIGH** | **FIXED** |
|
|
21
|
-
| F3 | SCIM payloads lack string length constraints | MEDIUM | Open |
|
|
22
|
-
| F4 | SCIM logs `ssoConfigId` query param not UUID-validated | MEDIUM | Open |
|
|
23
|
-
| F5 | `emailVerified` defaults to `true` when claim absent | MEDIUM | Documented |
|
|
24
|
-
| F6 | Domain DELETE query param not format-validated | LOW | Open |
|
|
25
|
-
| F7 | SCIM v2 `startIndex`/`count` not zod-validated | LOW | Open |
|
|
26
|
-
| F8 | State cookie not enforced as single-use | LOW | Accepted |
|
|
27
|
-
| F9 | Host header fallback in `toAbsoluteUrl` if `APP_URL` unset | LOW | Accepted |
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## 1. CSRF Protection -- PASS
|
|
32
|
-
|
|
33
|
-
All admin write endpoints are protected by:
|
|
34
|
-
- Cookie-based auth via `resolveSsoAdminContext()` with `requireAuth: true` and `requireFeatures` guards
|
|
35
|
-
- `SameSite: lax` on auth cookies (appropriate for SSO flows that require cross-site IdP redirects)
|
|
36
|
-
|
|
37
|
-
SCIM v2 endpoints use Bearer token authentication (inherently CSRF-safe).
|
|
38
|
-
|
|
39
|
-
**Files verified:** All routes under `api/config/`, `api/scim/tokens/`, and `api/scim/v2/`.
|
|
40
|
-
|
|
41
|
-
## 2. Replay Protection -- PASS
|
|
42
|
-
|
|
43
|
-
- **State cookie:** AES-256-GCM encrypted with HKDF-SHA256 key derivation, 12-byte random IV, 16-byte auth tag
|
|
44
|
-
- **TTL:** 5 minutes (checked at decryption time + cookie maxAge)
|
|
45
|
-
- **PKCE:** S256 with 32-byte random code verifier stored in encrypted state cookie
|
|
46
|
-
- **Nonce:** 16-byte random nonce validated by `openid-client` library against ID token
|
|
47
|
-
- **State comparison:** Timing-safe (`crypto.timingSafeEqual`) with length pre-check
|
|
48
|
-
- **Cleanup:** State cookie cleared after successful callback (maxAge: 0)
|
|
49
|
-
|
|
50
|
-
**F8 (LOW, Accepted):** No server-side single-use enforcement on state cookie. Mitigated by IdP's single-use authorization code policy (OAuth 2.0 spec requirement).
|
|
51
|
-
|
|
52
|
-
## 3. Tenant Isolation -- PASS (with F2 fixed)
|
|
53
|
-
|
|
54
|
-
- **Admin endpoints:** All queries scoped by `organizationId` for non-superadmins via `resolveSsoAdminContext()`
|
|
55
|
-
- **SCIM endpoints:** `organizationId` derived from verified bearer token, not from request parameters
|
|
56
|
-
- **HRD:** Intentional cross-org lookup (by design) -- only exposes `hasSso`, `configId`, `protocol`
|
|
57
|
-
- **F2 (FIXED):** `ScimTokenService.generateToken()` now verifies SSO config ownership before minting tokens
|
|
58
|
-
|
|
59
|
-
## 4. Token Security -- PASS (with F1 fixed)
|
|
60
|
-
|
|
61
|
-
- **OIDC client secrets:** Encrypted at rest via `TenantDataEncryptionService`; never exposed in API responses
|
|
62
|
-
- **SCIM tokens:** bcrypt-hashed (cost 10), prefix-indexed, timing-attack resistant (dummy hash on miss), raw value returned only once at creation
|
|
63
|
-
- **State cookies:** AES-256-GCM encrypted
|
|
64
|
-
- **F1 (FIXED):** Removed 5 `[SCIM DEBUG]` console.log statements that serialized full SCIM payloads (PII) to logs
|
|
65
|
-
|
|
66
|
-
## 5. Input Validation -- PASS (with notes)
|
|
67
|
-
|
|
68
|
-
- **Admin APIs:** All write endpoints validated with zod schemas from `data/validators.ts`
|
|
69
|
-
- **SCIM filter:** Strict regex parser with attribute allowlist, parameterized ORM queries
|
|
70
|
-
- **Domain validation:** DNS hostname regex, max 253 chars, must contain dot, max 20 per config
|
|
71
|
-
- **Return URL:** `sanitizeReturnUrl()` prevents open redirects (requires `/` prefix, rejects `//`, validates origin)
|
|
72
|
-
|
|
73
|
-
### Open Validation Items
|
|
74
|
-
|
|
75
|
-
- **F3 (MEDIUM):** SCIM payloads parsed via manual extraction without max-length constraints. Database column constraints provide backstop.
|
|
76
|
-
- **F4 (MEDIUM):** SCIM logs `ssoConfigId` param not UUID-validated (DB rejects non-UUID values).
|
|
77
|
-
- **F5 (MEDIUM, Documented):** `emailVerified` defaults to `true` when IdP omits the claim. Acceptable for enterprise IdPs (Entra, Google, Zitadel) which reliably send this claim. Domain allowlist provides mitigating control.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## Remediation Status
|
|
82
|
-
|
|
83
|
-
### Completed (This Milestone)
|
|
84
|
-
|
|
85
|
-
1. **F1** -- Removed all `[SCIM DEBUG]` console.log from `api/scim/v2/Users/route.ts` and `api/scim/v2/Users/[id]/route.ts`
|
|
86
|
-
2. **F2** -- Added `organizationId` filter to `ScimTokenService.generateToken()` in `services/scimTokenService.ts`
|
|
87
|
-
3. Removed 3 console.log statements from `lib/oidc-provider.ts` (raw ID token logging)
|
|
88
|
-
|
|
89
|
-
### Future Hardening (Priority 2)
|
|
90
|
-
|
|
91
|
-
4. **F3** -- Add `.slice(0, 255)` length constraints to SCIM mapper fields
|
|
92
|
-
5. **F4** -- Add `z.string().uuid()` validation to SCIM logs endpoint
|
|
93
|
-
6. **F5** -- Document `emailVerified` default behavior in admin guide
|
|
94
|
-
|
|
95
|
-
### Accepted Risks (Priority 3)
|
|
96
|
-
|
|
97
|
-
7. **F6-F9** -- Input validation consistency improvements and defense-in-depth
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Security Controls Summary
|
|
102
|
-
|
|
103
|
-
| Control | Status |
|
|
104
|
-
|---------|--------|
|
|
105
|
-
| Admin inputs validated (zod) | PASS |
|
|
106
|
-
| No hardcoded secrets | PASS |
|
|
107
|
-
| Auth on all admin endpoints | PASS |
|
|
108
|
-
| SCIM Bearer token auth | PASS |
|
|
109
|
-
| Parameterized ORM queries | PASS |
|
|
110
|
-
| HTTPS enforced (production) | PASS |
|
|
111
|
-
| CSRF protection (SameSite + encrypted state) | PASS |
|
|
112
|
-
| Error messages don't leak info | PASS |
|
|
113
|
-
| Client secrets encrypted at rest | PASS |
|
|
114
|
-
| SCIM tokens bcrypt-hashed | PASS |
|
|
115
|
-
| PKCE enforced | PASS |
|
|
116
|
-
| Open redirect protection | PASS |
|
|
117
|
-
| No PII in server logs | PASS (after F1 fix) |
|
|
118
|
-
| Org ownership on token generation | PASS (after F2 fix) |
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# Zitadel Setup Guide for Open Mercato SSO
|
|
2
|
-
|
|
3
|
-
This guide walks through setting up Zitadel as the identity provider for OIDC login and SCIM user provisioning in Open Mercato.
|
|
4
|
-
|
|
5
|
-
**Free tier**: Zitadel Cloud offers a free tier with up to 25,000 monthly active users.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Create a Zitadel Instance
|
|
10
|
-
|
|
11
|
-
1. Go to https://zitadel.com and sign up for a free account
|
|
12
|
-
2. Create a new instance (or use the default one)
|
|
13
|
-
3. Note your instance domain: `https://<instance>.zitadel.cloud`
|
|
14
|
-
|
|
15
|
-
## 2. Create Test Users
|
|
16
|
-
|
|
17
|
-
1. In the Zitadel Console, go to **Users** → **+ New**
|
|
18
|
-
2. Fill in:
|
|
19
|
-
- **Username**: e.g., `testuser@yourdomain.com`
|
|
20
|
-
- **First name** / **Last name**
|
|
21
|
-
- **Email**: the user's email address
|
|
22
|
-
- **Password**: set an initial password
|
|
23
|
-
3. Click **Create**
|
|
24
|
-
4. Repeat for 2-3 test users
|
|
25
|
-
|
|
26
|
-
## 3. Register the OIDC Application
|
|
27
|
-
|
|
28
|
-
1. In the Zitadel Console, go to **Projects** → **+ New**
|
|
29
|
-
2. Name the project `Open Mercato` and click **Continue**
|
|
30
|
-
3. Click **+ New Application**
|
|
31
|
-
4. Configure:
|
|
32
|
-
|
|
33
|
-
| Field | Value |
|
|
34
|
-
|-------|-------|
|
|
35
|
-
| **Name** | `Open Mercato` |
|
|
36
|
-
| **Type** | `Web` |
|
|
37
|
-
| **Authentication Method** | `Code (PKCE)` |
|
|
38
|
-
| **Redirect URIs** | `http://localhost:3000/api/sso/callback/oidc` |
|
|
39
|
-
| **Post-Logout URIs** | `http://localhost:3000/login` |
|
|
40
|
-
|
|
41
|
-
5. Click **Create**
|
|
42
|
-
6. On the application overview, note:
|
|
43
|
-
- **Client ID**
|
|
44
|
-
- **Client Secret** (generate one if using Code flow)
|
|
45
|
-
|
|
46
|
-
### OIDC Credentials Summary
|
|
47
|
-
|
|
48
|
-
| Credential | Where to find it | Value |
|
|
49
|
-
|------------|-----------------|-------|
|
|
50
|
-
| **Issuer URL** | Instance domain | `https://<instance>.zitadel.cloud` |
|
|
51
|
-
| **Client ID** | Application → General | Copy from console |
|
|
52
|
-
| **Client Secret** | Application → General → Generate | Copy immediately |
|
|
53
|
-
| **Redirect URI** | You configured this | `http://localhost:3000/api/sso/callback/oidc` |
|
|
54
|
-
|
|
55
|
-
### Configure Token Claims
|
|
56
|
-
|
|
57
|
-
Zitadel includes `email`, `given_name`, `family_name`, and `email_verified` in ID tokens by default when the `openid`, `profile`, and `email` scopes are requested. No additional configuration is needed.
|
|
58
|
-
|
|
59
|
-
### Assign Users
|
|
60
|
-
|
|
61
|
-
By default, all users in the organization can access the application. To restrict access:
|
|
62
|
-
|
|
63
|
-
1. Go to your Project → **Authorizations** → **+ New**
|
|
64
|
-
2. Select specific users or grant roles
|
|
65
|
-
3. Enable "Require authorization" on the project settings if you want to restrict access
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## 4. Create the SSO Config in Open Mercato
|
|
70
|
-
|
|
71
|
-
1. Log into Open Mercato as admin
|
|
72
|
-
2. Go to **Settings** → **Single Sign-On** → **Create New**
|
|
73
|
-
3. Select **OIDC** as the protocol
|
|
74
|
-
4. Enter:
|
|
75
|
-
- **Name**: `Zitadel`
|
|
76
|
-
- **Issuer URL**: `https://<instance>.zitadel.cloud`
|
|
77
|
-
- **Client ID**: (paste from Zitadel)
|
|
78
|
-
- **Client Secret**: (paste from Zitadel)
|
|
79
|
-
5. Add allowed email domains (e.g., `yourdomain.com`)
|
|
80
|
-
6. Test the connection (Verify Discovery)
|
|
81
|
-
7. Activate the config
|
|
82
|
-
|
|
83
|
-
### Verify OIDC Login
|
|
84
|
-
|
|
85
|
-
1. Open a private/incognito browser window
|
|
86
|
-
2. Go to the Open Mercato login page
|
|
87
|
-
3. Enter an email address belonging to one of your test users
|
|
88
|
-
4. The HRD check should detect SSO and redirect to Zitadel login
|
|
89
|
-
5. Authenticate at Zitadel
|
|
90
|
-
6. You should be redirected back to Open Mercato and logged in
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 5. Configure SCIM Provisioning
|
|
95
|
-
|
|
96
|
-
**Prerequisite**: Generate a SCIM bearer token from Open Mercato via the admin UI (SSO config → Provisioning tab → Generate Token).
|
|
97
|
-
|
|
98
|
-
### Zitadel SCIM Support
|
|
99
|
-
|
|
100
|
-
Zitadel supports outbound SCIM provisioning through its **Actions** feature (custom workflows). As of 2026, Zitadel also offers a native SCIM provisioning option:
|
|
101
|
-
|
|
102
|
-
1. Go to your Project → **Open Mercato** application
|
|
103
|
-
2. Navigate to **Provisioning** or **Actions**
|
|
104
|
-
3. Configure SCIM outbound provisioning:
|
|
105
|
-
|
|
106
|
-
| Field | Value |
|
|
107
|
-
|-------|-------|
|
|
108
|
-
| **SCIM Base URL** | `http://localhost:3000/api/sso/scim/v2` (dev) or `https://<your-domain>/api/sso/scim/v2` (prod) |
|
|
109
|
-
| **Bearer Token** | Paste the SCIM token from Open Mercato |
|
|
110
|
-
|
|
111
|
-
4. Test the connection
|
|
112
|
-
|
|
113
|
-
### Alternative: Manual/API-Based Provisioning
|
|
114
|
-
|
|
115
|
-
If Zitadel's native SCIM outbound is not available in your version, use the Zitadel Management API to sync users:
|
|
116
|
-
|
|
117
|
-
1. Create a Service User in Zitadel with Management API access
|
|
118
|
-
2. Use the Zitadel Management API to list users
|
|
119
|
-
3. Push user changes to Open Mercato's SCIM endpoint
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## 6. Test the Full Flow
|
|
124
|
-
|
|
125
|
-
### Test OIDC Login
|
|
126
|
-
|
|
127
|
-
1. Navigate to Open Mercato login
|
|
128
|
-
2. Enter a test user's email
|
|
129
|
-
3. **Expected**: Redirect to Zitadel → authenticate → redirect back to Open Mercato
|
|
130
|
-
4. Verify the user appears in the Open Mercato admin panel
|
|
131
|
-
|
|
132
|
-
### Test JIT Provisioning
|
|
133
|
-
|
|
134
|
-
If SCIM is not configured and JIT is enabled:
|
|
135
|
-
|
|
136
|
-
1. Log in as a new user via OIDC
|
|
137
|
-
2. **Expected**: User is automatically created in Open Mercato with `provisioningMethod: jit`
|
|
138
|
-
3. Verify user profile (name, email) matches Zitadel
|
|
139
|
-
|
|
140
|
-
### Test SCIM Provisioning (if configured)
|
|
141
|
-
|
|
142
|
-
1. Create a new user in Zitadel
|
|
143
|
-
2. Wait for provisioning cycle (or trigger manually)
|
|
144
|
-
3. **Expected**: User appears in Open Mercato with `provisioningMethod: scim`
|
|
145
|
-
4. Update the user in Zitadel → verify changes propagate
|
|
146
|
-
5. Deactivate the user in Zitadel → verify deactivation in Open Mercato
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Zitadel SCIM Quirks
|
|
151
|
-
|
|
152
|
-
| Quirk | Description | How to handle |
|
|
153
|
-
|-------|-------------|---------------|
|
|
154
|
-
| **Standard-compliant** | Zitadel follows SCIM 2.0 spec closely | Standard parsing works |
|
|
155
|
-
| **`email_verified` claim** | Always included in ID tokens | No special handling needed |
|
|
156
|
-
| **Group claims** | Available via project roles | Configure role mappings if needed |
|
|
157
|
-
| **PKCE support** | Natively supports S256 PKCE | Automatically used by Open Mercato |
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## Troubleshooting
|
|
162
|
-
|
|
163
|
-
### OIDC login redirects but fails
|
|
164
|
-
|
|
165
|
-
- Verify the Redirect URI matches exactly: `http://localhost:3000/api/sso/callback/oidc`
|
|
166
|
-
- Check that the Issuer URL matches your instance: `https://<instance>.zitadel.cloud`
|
|
167
|
-
- Verify Client ID and Client Secret
|
|
168
|
-
- Check the Zitadel Console → **Events** for error details
|
|
169
|
-
|
|
170
|
-
### "redirect_uri_mismatch" error
|
|
171
|
-
|
|
172
|
-
- Ensure the redirect URI registered in Zitadel matches exactly (including protocol and port)
|
|
173
|
-
- No trailing slash differences
|
|
174
|
-
- For production, use HTTPS
|
|
175
|
-
|
|
176
|
-
### Users can't log in
|
|
177
|
-
|
|
178
|
-
- Check that users exist in the same Zitadel organization
|
|
179
|
-
- If "Require authorization" is enabled on the project, ensure users have project grants
|
|
180
|
-
- Check that the email domain matches the allowed domains in Open Mercato SSO config
|
|
181
|
-
|
|
182
|
-
### SCIM connection fails
|
|
183
|
-
|
|
184
|
-
- For local dev, Zitadel needs to reach your server over the internet
|
|
185
|
-
- Use ngrok: `ngrok http 3000`
|
|
186
|
-
- Update the SCIM Base URL to the ngrok URL
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## Reference
|
|
191
|
-
|
|
192
|
-
- [Zitadel OIDC Documentation](https://zitadel.com/docs/guides/integrate/login/oidc)
|
|
193
|
-
- [Zitadel SCIM Documentation](https://zitadel.com/docs/guides/integrate/scim)
|
|
194
|
-
- [Zitadel Actions](https://zitadel.com/docs/guides/manage/customize/actions)
|
|
195
|
-
- [Zitadel Cloud](https://zitadel.com/pricing)
|