@tideorg/mcp 1.4.2 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/GAP_REGISTER.md +18 -8
- package/PRIVACY.md +41 -0
- package/README.md +207 -197
- package/adapters/AGENTS.md +3 -2
- package/adapters/CLAUDE.md +1 -1
- package/canon/anti-patterns.md +55 -62
- package/canon/concepts.md +46 -34
- package/canon/custom-contracts.md +12 -8
- package/canon/feature-mapping.md +27 -75
- package/canon/framework-matrix.md +69 -22
- package/canon/hosting-options.md +111 -0
- package/canon/iga-change-requests-api.md +211 -0
- package/canon/invariants.md +33 -35
- package/canon/security-gap-mapping.md +506 -0
- package/canon/security-runtime-probes.md +177 -0
- package/canon/tidecloak-bootstrap.md +21 -16
- package/canon/tidecloak-endpoints.md +60 -98
- package/canon/troubleshooting.md +115 -53
- package/canon/version-policy.md +8 -12
- package/mcp-server/dist/server.js +221 -19
- package/package.json +48 -45
- package/playbooks/add-auth-nextjs-existing.md +33 -17
- package/playbooks/add-auth-nextjs-fresh.md +1 -1
- package/playbooks/add-rbac-nextjs.md +7 -2
- package/playbooks/bootstrap-realm-from-template.md +33 -18
- package/playbooks/deploy-tidecloak-docker.md +31 -28
- package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
- package/playbooks/initialize-admin-and-link-account.md +22 -19
- package/playbooks/protect-api-nextjs.md +40 -1
- package/playbooks/protect-aspnet-core-asgard.md +313 -0
- package/playbooks/protect-routes-nextjs.md +24 -10
- package/playbooks/provision-tidecloak-skycloak.md +213 -0
- package/playbooks/setup-forseti-e2ee.md +32 -50
- package/playbooks/setup-iga-admin-panel.md +113 -171
- package/playbooks/verify-jwt-server-side.md +20 -1
- package/prompts/build-private-customer-portal.md +1 -1
- package/prompts/security-gap-analysis.md +55 -0
- package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
- package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
- package/reference-apps/encrypted-communication/scenario.md +2 -2
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
- package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
- package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
- package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
- package/reference-apps/iga-admin-governance/scenario.md +15 -13
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
- package/skills/tide-diagnostics/SKILL.md +1 -1
- package/skills/tide-integration/SKILL.md +9 -18
- package/skills/tide-mcp-qa/SKILL.md +139 -0
- package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
- package/skills/tide-reviewer/SKILL.md +1 -1
- package/skills/tide-security-analyst/SKILL.md +182 -0
- package/skills/tide-setup/SKILL.md +1 -1
- package/canon/delegation.md +0 -195
- package/playbooks/setup-server-delegation.md +0 -142
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Role: Security Analyst (Gap Analysis)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Analyze an **existing** system — one that may have no Tide involvement at all — and produce an honest security gap analysis that maps concrete weaknesses to the Tide capabilities that remove them. This is the pack's entry point for "here is my current app; where am I exposed, and what would Tide change?"
|
|
8
|
+
|
|
9
|
+
This role is **read-only and advisory**. It inspects and reports. It does not install Tide, write app code, or run bootstrap. When the operator accepts a finding, it hands off to the execution roles (`tide-setup`, `tide-integration`, `tide-route-and-api-protection`, `tide-rbac-and-e2ee`) via the normal team model (I-18).
|
|
10
|
+
|
|
11
|
+
It is the inverse of the rest of the pack: the pack builds Tide apps; this role audits non-Tide apps and shows where Tide fits.
|
|
12
|
+
|
|
13
|
+
## Two tiers: static, then runtime
|
|
14
|
+
|
|
15
|
+
The analysis runs in two tiers. Do the first always; do the second only with explicit authorization.
|
|
16
|
+
|
|
17
|
+
1. **Static (always)** — inspect code, config, and schema. Produces *candidate* findings, mostly tagged INFERRED. This tier never touches the running system.
|
|
18
|
+
2. **Runtime confirmation (opt-in, authorized)** — send a small number of non-destructive probes to the live target to confirm which candidates are real, upgrading INFERRED → VERIFIED (or dropping false positives). Governed entirely by `canon/security-runtime-probes.md`, including a hard authorization gate and non-exploitation limits. Do NOT run this tier unless the operator owns or is authorized to test the target and consents. When in doubt, stay static-only.
|
|
19
|
+
|
|
20
|
+
A static hypothesis and a runtime-confirmed finding are different strengths of evidence — never present the former as the latter.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Boundary
|
|
25
|
+
|
|
26
|
+
| This role owns | Does NOT own |
|
|
27
|
+
|----------------|--------------|
|
|
28
|
+
| Inspecting an existing codebase / deployment | Writing or modifying app code |
|
|
29
|
+
| Identifying trust concentrations | Running TideCloak bootstrap |
|
|
30
|
+
| Mapping gaps to Tide capabilities (`canon/security-gap-mapping.md`) | Installing the Tide SDK |
|
|
31
|
+
| Severity triage and evidence collection | Fixing the gaps it finds |
|
|
32
|
+
| Producing the gap-analysis report | Compliance review of Tide-built code (→ `tide-reviewer`) |
|
|
33
|
+
| Naming the remediation playbook sequence | Executing the remediation |
|
|
34
|
+
| Honestly scoping what Tide does NOT fix | Overstating Tide's coverage to close a sale |
|
|
35
|
+
|
|
36
|
+
**Hard rule**: This role never claims a security property Tide provides without an evidence tag and a source. It never presents Tide as fixing a gap listed in the "Out of scope" table of `canon/security-gap-mapping.md`.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## When to Trigger
|
|
41
|
+
|
|
42
|
+
- "Do a security analysis of my app / system."
|
|
43
|
+
- "Where is my current auth weak?"
|
|
44
|
+
- "What would Tide change about my security posture?"
|
|
45
|
+
- "Audit this codebase for identity/key/authorization gaps."
|
|
46
|
+
- "We're evaluating Tide — show me the gaps it would close in what we have."
|
|
47
|
+
- Before a migration, to justify and scope the work (`migrate-from-existing-auth`).
|
|
48
|
+
|
|
49
|
+
## When NOT to Trigger
|
|
50
|
+
|
|
51
|
+
- Building a new Tide app → `tide-setup` + team model (I-18).
|
|
52
|
+
- Reviewing already-written Tide code for compliance → `tide-reviewer`.
|
|
53
|
+
- Diagnosing a broken Tide login → `tide-diagnostics`.
|
|
54
|
+
- Full cryptographic red-team of a Tide construction → the internal `tide-crypto-redteam` skill (different surface: it attacks Tide's own crypto, this maps a customer's gaps).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Core Doctrine
|
|
59
|
+
|
|
60
|
+
1. **A gap is a named trust concentration, not a technology.** "Uses passwords" is not a finding. "Password verification collapses to trust in one database and one server process; a dump enables offline cracking of every account" is. Every finding must name the single party or artifact that, once compromised, defeats the control.
|
|
61
|
+
|
|
62
|
+
2. **Evidence before claim.** Every finding cites a concrete artifact — file path + line, config value, schema column, or a runtime observation (an HTTP response). Tag each: **VERIFIED** (observed directly), **INFERRED** (strongly implied by observed code), **ASSUMED** (operator statement, unconfirmed). No evidence → not a finding, at most a question.
|
|
63
|
+
|
|
64
|
+
3. **Honesty is the product.** The out-of-scope section is mandatory. Presenting Tide as fixing injection, XSS, or dependency CVEs destroys the credibility of the real findings. A visibly honest analysis is more persuasive than an inflated one. (AP-SEC-1)
|
|
65
|
+
|
|
66
|
+
4. **Map to capability, not to slogans.** Each gap maps to a specific Tide mechanism with sourcing from `canon/feature-mapping.md` / `canon/invariants.md`, and to a real playbook in `playbooks/`. If no playbook exists, say so and point at `GAP_REGISTER.md` — do not improvise.
|
|
67
|
+
|
|
68
|
+
5. **Severity is about blast radius under single compromise**, per the rubric in `canon/security-gap-mapping.md`. Ask: "If exactly one party here is compromised, what does the attacker get?"
|
|
69
|
+
|
|
70
|
+
6. **Confirm the mode before claiming a cryptographic property.** IGA/QEA has two modes: Tide (`iga.attestor=tide`, licensed — cryptographic) and Tideless (`iga.attestor=simple`/unset, default — username attestation, server-enforced, no crypto). Findings SG-07/SG-14/SG-16 lean on IGA; their strong "no single bypass / tamper-evident" claims hold **only in Tide mode**. Never assert them for a Tideless realm. Same discipline for any capability whose guarantee is mode- or config-dependent — verify the target's actual configuration, don't assume the strongest variant. See the IGA-model note in `canon/security-gap-mapping.md`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Execution
|
|
75
|
+
|
|
76
|
+
### Step 1 — Scope and consent
|
|
77
|
+
Confirm what you may inspect (repo, running instance, config) and that you are authorized to analyze it. State the boundary: read-only, no changes. If a live target is in scope, confirm it is the operator's own system or an authorized engagement.
|
|
78
|
+
|
|
79
|
+
### Step 2 — Inventory the trust architecture
|
|
80
|
+
Before grepping for patterns, answer four questions from the code/config:
|
|
81
|
+
- **Identity**: How are users authenticated? Where do credentials live?
|
|
82
|
+
- **Authority**: How are tokens/sessions signed? Who holds the signing key?
|
|
83
|
+
- **Authorization**: Where is the access decision made — client, server, or both?
|
|
84
|
+
- **Data**: What sensitive data exists, and who can read it in the clear?
|
|
85
|
+
|
|
86
|
+
### Step 3 — Run the static gap sweep
|
|
87
|
+
Load `canon/security-gap-mapping.md`. Work through SG-01 … SG-18. For each, run the detection commands against the target and record hits with evidence. Do NOT stop at the first finding — enumerate exhaustively (especially SG-05, where a sampled API audit gives false assurance).
|
|
88
|
+
|
|
89
|
+
Also collect anything matching the "Out of scope" table — you will report these separately.
|
|
90
|
+
|
|
91
|
+
### Step 4 — Triage
|
|
92
|
+
For each candidate gap:
|
|
93
|
+
- Name the trust concentration.
|
|
94
|
+
- Assign severity (CRITICAL / HIGH / MEDIUM / INFO).
|
|
95
|
+
- Attach evidence with a confidence tag (mostly INFERRED at this point).
|
|
96
|
+
- Note gap interactions (e.g. SG-02 + SG-07: one admin who also controls the signing key is worse than either alone; SG-13 nullifies SG-02's protection entirely; SG-07 + SG-14: an admin who acts *and* erases the trail).
|
|
97
|
+
|
|
98
|
+
### Step 4b — Runtime confirmation (opt-in, only if authorized)
|
|
99
|
+
If — and only if — the operator owns or is authorized to test a live target and consents, run the runtime tier per `canon/security-runtime-probes.md`. Confirm the authorization gate first. Send the small set of non-destructive probes for the candidate findings, upgrade confirmed ones to VERIFIED with a Runtime confirmation line, and downgrade/drop those a probe refutes. Never mutate data, never reach beyond the operator's own test account, never exploit an out-of-scope class to "confirm" it. If not authorized, skip this step and keep findings at their static confidence.
|
|
100
|
+
|
|
101
|
+
### Step 5 — Map to Tide
|
|
102
|
+
For each in-scope gap, pull the Tide replacement and remediation path from `canon/security-gap-mapping.md`. Verify each named playbook exists (`tide_list playbooks`). Carry the **honesty note** for each — the limits of the Tide replacement are part of the finding, not an appendix.
|
|
103
|
+
|
|
104
|
+
### Step 6 — Report
|
|
105
|
+
Emit the report in the format below. Lead with the trust-architecture summary and the highest-severity findings. Put the honest out-of-scope section in the body, not buried.
|
|
106
|
+
|
|
107
|
+
### Step 7 — Hand off
|
|
108
|
+
Offer the remediation sequence. If the operator accepts, hand to the execution roles via I-18 (Scenario Resolver → Setup → Application → Security → IAM/Policy → Reviewer). Do not begin remediation from this role.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Report Format
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
# Tide Security Gap Analysis — <system name>
|
|
116
|
+
Scope: <what was inspected> | Authorization: <confirmed by whom>
|
|
117
|
+
Method: static (repo) | both (static + runtime) — list which SG findings were probed
|
|
118
|
+
|
|
119
|
+
## Trust Architecture
|
|
120
|
+
- Identity: <where credentials live>
|
|
121
|
+
- Authority: <who holds signing keys>
|
|
122
|
+
- Authorization: <where the decision is made>
|
|
123
|
+
- Sensitive data: <what, readable by whom>
|
|
124
|
+
|
|
125
|
+
## Findings (most severe first)
|
|
126
|
+
### [SEVERITY] SG-XX — <title>
|
|
127
|
+
- Trust concentration: <the single party/artifact>
|
|
128
|
+
- Evidence: <file:line / config / HTTP obs> [VERIFIED|INFERRED|ASSUMED]
|
|
129
|
+
- Runtime confirmation: <exact request → observed response> [VERIFIED] (omit if static-only or not probed)
|
|
130
|
+
- Failure scenario: <what one compromise yields>
|
|
131
|
+
- Tide replacement: <mechanism> (source: canon/...) [sourcing tag]
|
|
132
|
+
- Remediation: <playbook sequence>
|
|
133
|
+
- Limits: <honesty note — what this does NOT cover>
|
|
134
|
+
|
|
135
|
+
## Gap Interactions
|
|
136
|
+
<pairs that compound, e.g. SG-02 + SG-07>
|
|
137
|
+
|
|
138
|
+
## Not Addressed by Tide (found in scope)
|
|
139
|
+
<injection / XSS / deps / rate-limiting / etc., with standard remediation pointers>
|
|
140
|
+
<or: "None observed in the inspected scope.">
|
|
141
|
+
|
|
142
|
+
## Recommended Remediation Sequence
|
|
143
|
+
1. <playbook> — closes <SG-XX>
|
|
144
|
+
2. ...
|
|
145
|
+
Handoff: <execution role to engage first, per I-18>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Verification
|
|
151
|
+
|
|
152
|
+
This analysis is complete only when:
|
|
153
|
+
- [ ] Every SG in `canon/security-gap-mapping.md` (SG-01 … SG-18) was checked (not sampled), or its non-applicability is stated.
|
|
154
|
+
- [ ] Every finding names a trust concentration and carries evidence with a confidence tag.
|
|
155
|
+
- [ ] Every Tide-replacement claim cites `canon/feature-mapping.md` or `canon/invariants.md`.
|
|
156
|
+
- [ ] Every remediation names a playbook that actually exists.
|
|
157
|
+
- [ ] The out-of-scope section is present and non-empty (or explicitly states none found).
|
|
158
|
+
- [ ] No finding claims Tide fixes an out-of-scope gap class.
|
|
159
|
+
- [ ] If the runtime tier ran: authorization was confirmed, every probe was non-destructive and confined to the operator's own account, and probed findings carry a Runtime confirmation line. Unprobed findings remain at their static confidence.
|
|
160
|
+
|
|
161
|
+
## Anti-Patterns
|
|
162
|
+
|
|
163
|
+
- **AP-SEC-1** — Presenting Tide as fixing injection/XSS/CSRF/deps/rate-limiting. Kills credibility of the real findings.
|
|
164
|
+
- **AP-SEC-2** — A trust-concentration claim with no evidence artifact. Every finding needs a file, config value, or runtime observation.
|
|
165
|
+
- **AP-SEC-3** — Sampling APIs instead of enumerating them (SG-05). A "clean" sampled audit is worse than no audit — it manufactures false assurance.
|
|
166
|
+
- **AP-SEC-4** — Relocating a single point instead of removing it and calling it a fix. Moving a JWT secret from env to KMS is not what SG-02 asks for; threshold signing removes the whole-key.
|
|
167
|
+
- **AP-SEC-5** — Overstating severity to motivate migration. Report SG-10 (remote JWKS over TLS) as a hardening delta of the Tide model, not as a live vulnerability, unless TLS is also broken.
|
|
168
|
+
- **AP-SEC-6** — Beginning remediation from this role. Analyst reports; execution roles build. Do not blur the boundary.
|
|
169
|
+
- Runtime-tier anti-patterns (AP-SEC-7 … AP-SEC-10) live in `canon/security-runtime-probes.md`: no unauthorized probing, no mutating "checks", no escalation into exploitation, no runtime-level confidence on an unprobed hypothesis.
|
|
170
|
+
|
|
171
|
+
## Handoff Trace
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
[TRACE]
|
|
175
|
+
Scenario: security gap analysis of existing (non-Tide) system
|
|
176
|
+
Role: Security Analyst
|
|
177
|
+
Reason: <operator requested audit / migration scoping>
|
|
178
|
+
Preconditions: read-only scope confirmed, authorization confirmed
|
|
179
|
+
Findings: <N gaps, severities>
|
|
180
|
+
Next: <Setup / Scenario Resolver for remediation, or "report only — awaiting operator decision">
|
|
181
|
+
[/TRACE]
|
|
182
|
+
```
|
|
@@ -33,7 +33,7 @@ If multiple paths are plausible and the repo does not resolve it, ask the user b
|
|
|
33
33
|
## When NOT to Trigger
|
|
34
34
|
|
|
35
35
|
- App already has working Tide auth (provider present, adapter JSON loaded, login functional). Proceed to route-and-api-protection or rbac-and-e2ee skill instead.
|
|
36
|
-
- User is asking about
|
|
36
|
+
- User is asking about a **direct Keycloak-to-TideCloak** migration. That specific path is not yet documented (GAP-023) — say so and stop. (Retrofitting an app off generic-OIDC / NextAuth / Clerk is a different task, covered by `playbooks/migrate-from-existing-auth.md`; the open gap is the KC→TideCloak realm/data migration itself.)
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
package/canon/delegation.md
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# Server-Side Delegation - Canon
|
|
2
|
-
|
|
3
|
-
Canonical reference for server-side admin API calls via delegation tokens.
|
|
4
|
-
|
|
5
|
-
When your app server needs to call TideCloak admin APIs (manage users, roles, policies), it cannot use the browser's DPoP-bound access token. The delegation flow issues a new token bound to the server's ephemeral key.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Packages
|
|
10
|
-
|
|
11
|
-
| Package | Side | Purpose |
|
|
12
|
-
|---------|------|---------|
|
|
13
|
-
| `@tidecloak/server` | Server | `TideDelegation` class, middleware, DPoP proof generation |
|
|
14
|
-
| `@tidecloak/js` | Browser | `createTideFetch` wrapper, `signDelegationRequest`, `signDpopApproval` |
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Flow: Forgetful Interrupt Pattern
|
|
19
|
-
|
|
20
|
-
1. Browser calls server endpoint (e.g., `GET /api/admin/users`)
|
|
21
|
-
2. Server `requireDelegation()` middleware checks cache. No cached token.
|
|
22
|
-
3. Server generates ephemeral Ed25519 key, responds **419** with challenge
|
|
23
|
-
4. `createTideFetch` intercepts 419, calls `IAMService.signDelegationRequest(payload)` and `IAMService.signDpopApproval(serverJkt)`
|
|
24
|
-
5. Browser POSTs signed artifacts to `/api/delegation`
|
|
25
|
-
6. Server `handleDelegation()` exchanges with TideCloak token endpoint
|
|
26
|
-
7. TideCloak validates chain of trust, signs delegation token via ORK network
|
|
27
|
-
8. Server caches delegation token, responds `{ delegated: true }`
|
|
28
|
-
9. `createTideFetch` retries original request. Server finds cached token, proceeds.
|
|
29
|
-
|
|
30
|
-
Subsequent requests use the cached token until it expires.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Setup
|
|
35
|
-
|
|
36
|
-
### Server
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
import { TideDelegation } from '@tidecloak/server';
|
|
40
|
-
|
|
41
|
-
const delegation = new TideDelegation({
|
|
42
|
-
tidecloakUrl: process.env.TIDECLOAK_URL,
|
|
43
|
-
realm: process.env.TIDECLOAK_REALM,
|
|
44
|
-
clientId: process.env.TIDECLOAK_CLIENT_ID,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Exchange endpoint
|
|
48
|
-
app.post('/api/delegation', authenticate, delegation.handleDelegation());
|
|
49
|
-
|
|
50
|
-
// Protected route with delegation
|
|
51
|
-
app.get('/api/admin/users',
|
|
52
|
-
authenticate,
|
|
53
|
-
delegation.requireDelegation(),
|
|
54
|
-
async (req, res) => {
|
|
55
|
-
const users = await req.delegation.fetch(adminUrl + '/users');
|
|
56
|
-
res.json(users);
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Browser
|
|
62
|
-
|
|
63
|
-
```js
|
|
64
|
-
import { createTideFetch } from '@tidecloak/js';
|
|
65
|
-
|
|
66
|
-
const appFetch = createTideFetch(window.fetch);
|
|
67
|
-
|
|
68
|
-
// Delegation is transparent
|
|
69
|
-
const users = await appFetch('/api/admin/users');
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Scoped Delegation
|
|
75
|
-
|
|
76
|
-
Limit roles in the delegation token. If `roles` is specified, only listed roles/clients appear. Everything else is stripped.
|
|
77
|
-
|
|
78
|
-
```js
|
|
79
|
-
// Only read role for my-app client
|
|
80
|
-
delegation.requireDelegation({
|
|
81
|
-
roles: {
|
|
82
|
-
clients: { 'my-app': ['read'] }
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// Realm + multiple clients
|
|
87
|
-
delegation.requireDelegation({
|
|
88
|
-
roles: {
|
|
89
|
-
realm: ['admin'],
|
|
90
|
-
clients: {
|
|
91
|
-
'my-app': ['read', 'write'],
|
|
92
|
-
'other-app': ['view']
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
// No filtering (default)
|
|
98
|
-
delegation.requireDelegation()
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Rules:
|
|
102
|
-
- Omitting `roles` entirely keeps all user roles
|
|
103
|
-
- Once `roles` is specified, unlisted realms/clients are stripped
|
|
104
|
-
- Cannot escalate. Requesting a role the user does not have silently drops it.
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Token Exchange Endpoint
|
|
109
|
-
|
|
110
|
-
The server exchanges artifacts at TideCloak's standard OIDC token endpoint:
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
POST /realms/{realm}/protocol/openid-connect/token
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Parameters (form-encoded):
|
|
117
|
-
- `grant_type`: `urn:ietf:params:oauth:grant-type:token-exchange`
|
|
118
|
-
- `client_id`: app client ID
|
|
119
|
-
- `subject_token`: user's access token
|
|
120
|
-
- `subject_token_type`: `urn:ietf:params:oauth:token-type:access_token`
|
|
121
|
-
- `actor_token`: browser-signed delegation request JWT
|
|
122
|
-
- `actor_token_type`: `urn:ietf:params:oauth:token-type:jwt`
|
|
123
|
-
- `dpop_approval`: session key approval of server's DPoP key
|
|
124
|
-
|
|
125
|
-
Headers:
|
|
126
|
-
- `DPoP`: server's DPoP proof JWT (Ed25519, contains server's public key)
|
|
127
|
-
|
|
128
|
-
Response: `{ access_token, token_type: "DPoP", expires_in, issued_token_type }`
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Chain of Trust
|
|
133
|
-
|
|
134
|
-
Two-hop validation ensures the delegation token is legitimately authorized:
|
|
135
|
-
|
|
136
|
-
**HOP 1**: `subject_token.cnf.jkt == thumbprint(BROWSER_KEY from actor_token header)`
|
|
137
|
-
The browser that owns the access token is the same one that signed the delegation request.
|
|
138
|
-
|
|
139
|
-
**HOP 2**: `actor_token.cnf.jkt == thumbprint(SERVER_KEY from DPoP header)`
|
|
140
|
-
The delegation request authorizes the server's specific ephemeral key.
|
|
141
|
-
|
|
142
|
-
Validated at two layers:
|
|
143
|
-
1. TideCloak authenticator (Java) - before token is built
|
|
144
|
-
2. ORK signing model (C#) - before token is threshold-signed
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## Using req.delegation.fetch()
|
|
149
|
-
|
|
150
|
-
The `fetch` helper on `req.delegation` handles DPoP proof generation and Authorization headers:
|
|
151
|
-
|
|
152
|
-
```js
|
|
153
|
-
// GET
|
|
154
|
-
const users = await req.delegation.fetch(url);
|
|
155
|
-
|
|
156
|
-
// POST with JSON
|
|
157
|
-
await req.delegation.fetch(url, { method: 'POST', body: { name: 'role' } });
|
|
158
|
-
|
|
159
|
-
// PUT
|
|
160
|
-
await req.delegation.fetch(url, { method: 'PUT', body: { enabled: true } });
|
|
161
|
-
|
|
162
|
-
// DELETE
|
|
163
|
-
await req.delegation.fetch(url, { method: 'DELETE' });
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Anti-Patterns
|
|
169
|
-
|
|
170
|
-
- **Do not use master admin credentials** for server-side admin calls. Use delegation.
|
|
171
|
-
- **Do not forward the user's browser token** to admin API. It is DPoP-bound to the browser's key.
|
|
172
|
-
- **Do not skip DPoP on delegation requests**. The delegation token is DPoP-bound to the server's ephemeral key. Every admin API call needs a fresh DPoP proof with `ath` claim.
|
|
173
|
-
- **Do not share delegation tokens across users**. They are cached per session ID.
|
|
174
|
-
- **Do not hardcode server keys**. They are ephemeral Ed25519 keys, generated per session.
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Prerequisites
|
|
179
|
-
|
|
180
|
-
- TideCloak with `tide-chain-of-trust` client authenticator enabled (added by default in recent TideCloak builds)
|
|
181
|
-
- `DelegationToken:1` in the VRK model whitelist (requires VRK rotation after adding)
|
|
182
|
-
- DPoP enabled on the client
|
|
183
|
-
- `@tidecloak/server` installed on the backend
|
|
184
|
-
- `createTideFetch` wrapping fetch on the frontend
|
|
185
|
-
- An `authenticate` middleware that sets `req.accessToken` from the Authorization header
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## Invariants
|
|
190
|
-
|
|
191
|
-
- Delegation tokens are short-lived (max 600s / 10 minutes)
|
|
192
|
-
- Server key is ephemeral per session, cleared when delegation cache expires
|
|
193
|
-
- The browser must approve each server key via ORK enclave (DPoP approval)
|
|
194
|
-
- Scoped delegation can only reduce roles, never add them
|
|
195
|
-
- Delegation tokens have `act: { sub: clientId }` per RFC 8693
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# Setup Server-Side Delegation
|
|
2
|
-
|
|
3
|
-
For apps where the backend needs to call TideCloak admin APIs on behalf of authenticated users.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## When to Use
|
|
8
|
-
|
|
9
|
-
- Your server manages users, roles, or policies via TideCloak admin API
|
|
10
|
-
- You need server-side role management or access approvals
|
|
11
|
-
- You want to avoid using master admin credentials from your server
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Prerequisites
|
|
16
|
-
|
|
17
|
-
- TideCloak bootstrapped and running
|
|
18
|
-
- `tidecloak.json` exported with `jwk`, `vendorId`, `homeOrkUrl`
|
|
19
|
-
- App has working Tide login (provider wired, DPoP enabled)
|
|
20
|
-
- Node.js 18+ on the server (Ed25519 support)
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Steps
|
|
25
|
-
|
|
26
|
-
### 1. Install server SDK
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install @tidecloak/server
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### 2. Create delegation instance
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
// server/lib/delegation.ts
|
|
36
|
-
import { TideDelegation } from '@tidecloak/server';
|
|
37
|
-
|
|
38
|
-
let _delegation: TideDelegation | null = null;
|
|
39
|
-
|
|
40
|
-
export function getDelegation(): TideDelegation {
|
|
41
|
-
if (!_delegation) {
|
|
42
|
-
_delegation = new TideDelegation({
|
|
43
|
-
tidecloakUrl: process.env.TIDECLOAK_URL || config['auth-server-url'],
|
|
44
|
-
realm: process.env.TIDECLOAK_REALM || config.realm,
|
|
45
|
-
clientId: process.env.TIDECLOAK_CLIENT_ID || config.resource,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return _delegation;
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Use a lazy singleton. Do not create a new instance per request.
|
|
53
|
-
|
|
54
|
-
### 3. Wire delegation exchange endpoint
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
app.post('/api/delegation', authenticate, getDelegation().handleDelegation());
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This endpoint receives signed delegation artifacts from the browser and exchanges them with TideCloak for a delegation token.
|
|
61
|
-
|
|
62
|
-
### 4. Add delegation middleware to admin routes
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
app.get('/api/admin/users',
|
|
66
|
-
authenticate,
|
|
67
|
-
requireAdmin,
|
|
68
|
-
getDelegation().requireDelegation(),
|
|
69
|
-
async (req, res) => {
|
|
70
|
-
const adminUrl = `${tidecloakUrl}/admin/realms/${realm}`;
|
|
71
|
-
const users = await req.delegation.fetch(`${adminUrl}/users`);
|
|
72
|
-
res.json(users);
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The middleware:
|
|
78
|
-
- Checks if a cached delegation token exists for this user session
|
|
79
|
-
- If yes: attaches `req.delegation` with `token` and `fetch()` helper
|
|
80
|
-
- If no: responds 419 with a delegation challenge
|
|
81
|
-
|
|
82
|
-
### 5. Wrap browser fetch with createTideFetch
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
import { createTideFetch } from '@tidecloak/js';
|
|
86
|
-
|
|
87
|
-
// Wrap the app's fetch function
|
|
88
|
-
const appFetch = createTideFetch(window.fetch);
|
|
89
|
-
|
|
90
|
-
// Use appFetch for all server calls that may need delegation
|
|
91
|
-
const users = await appFetch('/api/admin/users');
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`createTideFetch` intercepts 419 responses, handles the delegation signing flow with the browser's DPoP key, and retries the original request.
|
|
95
|
-
|
|
96
|
-
### 6. Optional: scope delegation roles
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
// Only include specific roles in the delegation token
|
|
100
|
-
getDelegation().requireDelegation({
|
|
101
|
-
roles: {
|
|
102
|
-
realm: ['admin'],
|
|
103
|
-
clients: { 'my-app': ['manage-users'] }
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
If `roles` is specified, only listed roles appear in the delegation token. Unlisted realms/clients are stripped. Cannot add roles the user does not have.
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## Verification
|
|
113
|
-
|
|
114
|
-
- [ ] `@tidecloak/server` in server package.json
|
|
115
|
-
- [ ] `/api/delegation` POST route wired with `handleDelegation()`
|
|
116
|
-
- [ ] Admin routes use `requireDelegation()` middleware
|
|
117
|
-
- [ ] Browser uses `createTideFetch` wrapper
|
|
118
|
-
- [ ] Admin API calls succeed: user list, role management, etc.
|
|
119
|
-
- [ ] 419 responses are handled transparently (no user-visible interruption)
|
|
120
|
-
- [ ] Delegation token is cached (no 419 on every request)
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Troubleshooting
|
|
125
|
-
|
|
126
|
-
**Repeated 419 responses**
|
|
127
|
-
Check that `/api/delegation` is wired correctly and the `authenticate` middleware sets `req.accessToken`.
|
|
128
|
-
|
|
129
|
-
**"No pending delegation challenge"**
|
|
130
|
-
Server restarted between 419 and delegation POST. Browser retries automatically.
|
|
131
|
-
|
|
132
|
-
**"Delegation exchange failed"**
|
|
133
|
-
Check TideCloak has `tide-chain-of-trust` client authenticator enabled and `DelegationToken:1` in VRK model whitelist. A VRK rotation is needed after adding the model ID.
|
|
134
|
-
|
|
135
|
-
**"DPoP is not initialized"**
|
|
136
|
-
`signDelegationRequest` needs the DPoP provider. Ensure `useDPoP` is in the TideCloakProvider config object.
|
|
137
|
-
|
|
138
|
-
**Delegation token has wrong roles**
|
|
139
|
-
If using scoped delegation, everything not listed is excluded. Omit `roles` for full permissions.
|
|
140
|
-
|
|
141
|
-
**Admin API returns 403**
|
|
142
|
-
The user does not have the required role (e.g., `tide-realm-admin` on `realm-management`). Delegation can only use roles the user already has.
|