@tideorg/mcp 1.4.1 → 1.9.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/CHANGELOG.md +81 -0
- package/GAP_REGISTER.md +19 -9
- package/PRIVACY.md +41 -0
- package/README.md +204 -120
- package/adapters/AGENTS.md +3 -2
- package/adapters/CLAUDE.md +1 -1
- package/adapters/replit.md +0 -0
- 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/redirect-handler.md +0 -0
- package/canon/security-gap-mapping.md +506 -0
- package/canon/security-runtime-probes.md +177 -0
- package/canon/tidecloak-bootstrap.md +84 -15
- package/canon/tidecloak-endpoints.md +60 -98
- package/canon/troubleshooting.md +201 -53
- package/canon/version-policy.md +8 -12
- package/mcp-server/dist/http.d.ts +2 -0
- package/mcp-server/dist/http.js +46 -0
- package/mcp-server/dist/index.d.ts +0 -0
- package/mcp-server/dist/index.js +2 -601
- package/mcp-server/dist/server.d.ts +2 -0
- package/mcp-server/dist/server.js +617 -0
- 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/configure-e2ee-roles-and-policies.md +0 -0
- package/playbooks/deploy-tidecloak-docker.md +31 -28
- package/playbooks/diagnose-broken-login.md +0 -0
- package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
- package/playbooks/initialize-admin-and-link-account.md +22 -19
- package/playbooks/migrate-from-existing-auth.md +0 -0
- 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/start-tidecloak-dev.md +0 -0
- package/playbooks/verify-jwt-server-side.md +20 -1
- package/prompts/add-admin-approval-flow.md +0 -0
- package/prompts/build-private-customer-portal.md +1 -1
- package/prompts/migrate-generic-auth-to-tide.md +0 -0
- package/prompts/secure-existing-app.md +0 -0
- package/prompts/security-gap-analysis.md +55 -0
- package/reference-apps/INDEX.md +0 -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/manifest.yaml +0 -0
- package/reference-apps/encrypted-communication/role-policy-matrix.md +0 -0
- package/reference-apps/encrypted-communication/scenario.md +2 -2
- package/reference-apps/git-pr-signing-service/anti-patterns.md +0 -0
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
- package/reference-apps/git-pr-signing-service/manifest.yaml +0 -0
- package/reference-apps/git-pr-signing-service/role-policy-matrix.md +0 -0
- package/reference-apps/git-pr-signing-service/scenario.md +0 -0
- 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/manifest.yaml +0 -0
- 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/anti-patterns.md +0 -0
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
- package/reference-apps/organisation-password-manager/manifest.yaml +0 -0
- package/reference-apps/organisation-password-manager/role-policy-matrix.md +0 -0
- package/reference-apps/organisation-password-manager/scenario.md +0 -0
- package/reference-apps/policy-governed-signing/anti-patterns.md +0 -0
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
- package/reference-apps/policy-governed-signing/manifest.yaml +0 -0
- package/reference-apps/policy-governed-signing/role-policy-matrix.md +0 -0
- package/reference-apps/policy-governed-signing/scenario.md +0 -0
- package/skills/tide-diagnostics/SKILL.md +1 -1
- package/skills/tide-integration/SKILL.md +9 -18
- package/skills/tide-learning-capture/SKILL.md +0 -0
- 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-route-and-api-protection/SKILL.md +0 -0
- package/skills/tide-scenario-resolver/SKILL.md +0 -0
- package/skills/tide-security-analyst/SKILL.md +182 -0
- package/skills/tide-setup/SKILL.md +1 -1
- package/skills/tide-solutions-architect/SKILL.md +0 -0
- package/canon/delegation.md +0 -195
- package/playbooks/setup-server-delegation.md +0 -142
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Protect an ASP.NET Core API with TideCloak (via Tide.Asgard.AspNetCore)
|
|
2
|
+
|
|
3
|
+
Wire TideCloak authentication into an ASP.NET Core resource server using the **Tide.Asgard.AspNetCore** SDK. Validates Tidecloak-issued EdDSA JWTs locally and performs RFC 8693 OAuth Token Exchange when your API needs to call another protected service.
|
|
4
|
+
|
|
5
|
+
This playbook supersedes the stock `Microsoft.AspNetCore.Authentication.OpenIdConnect` approach floated in PUC-092 / PUC-093. The asgard SDK ships Tide-specific helpers (`Utils.GetEd25519IssuerKey`, `AddTokenExchange`, EdDSA `SignatureProvider`) that remove most of the manual JWKS wiring the OIDC path required.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
> ## Current Status (temporary — 2026-05-14)
|
|
10
|
+
>
|
|
11
|
+
> The asgard .NET SDK only works against TideCloak realms where **IGA is OFF**. This is a hard constraint at present, not a recommendation.
|
|
12
|
+
>
|
|
13
|
+
> Implications:
|
|
14
|
+
> - Use the **staging** TideCloak Docker image: `tideorg/tidecloak-stg-dev`. The non-staging dev image does not give you the auth flow the asgard SDK targets.
|
|
15
|
+
> - Run the realm in **BYOiD-only mode** (license applied, IGA not toggled on). This is documented as a valid deployment mode in [canon/concepts.md](../canon/concepts.md) — "BYOiD-only (license + no IGA)".
|
|
16
|
+
> - **No `tide-realm-admin` role assignment is needed** for the admin user. That role is part of the IGA quorum machinery and only relevant when IGA is on. With IGA off, normal Keycloak admin permissions are sufficient.
|
|
17
|
+
> - **No `toggle-iga` call** during realm setup. Stop the canonical bootstrap order (license → IGA → E2EE) at the licence step.
|
|
18
|
+
> - E2EE is not available in this mode (E2EE requires IGA). Asgard .NET today is auth + token-exchange only, so this matches the SDK's surface.
|
|
19
|
+
>
|
|
20
|
+
> When the SDK adds IGA support, this section will be removed and the prerequisites updated. Tracking note: [notes/asgard-dotnet-current-status.md](../notes/asgard-dotnet-current-status.md).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## When to Use
|
|
25
|
+
|
|
26
|
+
- You have an ASP.NET Core (.NET 10) API that needs to validate JWTs issued by Tidecloak.
|
|
27
|
+
- Your front end is a Tide-aware SPA (uses `@tidecloak/js`) and your backend is C#.
|
|
28
|
+
- You need server-to-server token exchange (RFC 8693) — e.g. your API calls a downstream protected service on behalf of the user.
|
|
29
|
+
|
|
30
|
+
## When NOT to Use
|
|
31
|
+
|
|
32
|
+
- Your stack is Next.js / React / Vanilla JS — use [protect-api-nextjs.md](protect-api-nextjs.md) / [verify-jwt-server-side.md](verify-jwt-server-side.md) instead.
|
|
33
|
+
- You need browser-side **DPoP**: the asgard `.WithDPoP(...)` registration is commented out at HEAD (see [Common Failures](#common-failures)).
|
|
34
|
+
- You need to **build, sign, or test Tide Policies / contracts** server-side from .NET. The asgard .NET SDK does not include the TS `asgard-tide` wire-format library; policy signing today lives in the JS stack (`heimdall-tide` + `@tidecloak/js`).
|
|
35
|
+
|
|
36
|
+
## Status classifications
|
|
37
|
+
|
|
38
|
+
- `Tide.Asgard.AspNetCore.Authentication` v0.1.0 — **VERIFIED** against `sources/asgard-sdk/aspnet/Tide.Asgard.AspNetCore/Tide.Asgard.AspNetCore/`.
|
|
39
|
+
- `Tide.Asgard.Core` v0.1.0 — **VERIFIED**. Provides EdDSA `SignatureProvider` over `Microsoft.IdentityModel.Tokens 8.15.0` and `BouncyCastle.Cryptography 2.6.2`.
|
|
40
|
+
- `Tide.Asgard.AspNetCore.DPoP` — **OBSERVED_PATTERN**. Built and ProjectReferenced by the Example, but no `<PackageId>` is set and the registration helper `.WithDPoP(...)` is commented out. Validation primitives exist; usage requires manual service registration.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Prerequisites
|
|
45
|
+
|
|
46
|
+
- **.NET 10 SDK** (preview-tier). Every csproj pins `<TargetFramework>net10.0</TargetFramework>`.
|
|
47
|
+
- TideCloak running using the **staging** Docker image (`tideorg/tidecloak-stg-dev`) — see Current Status above and [deploy-tidecloak-docker.md](deploy-tidecloak-docker.md).
|
|
48
|
+
- A realm licensed via `setUpTideRealm`, with **IGA NOT toggled on** (BYOiD-only mode). Do NOT run `toggle-iga` for an asgard-.NET-backed app today.
|
|
49
|
+
- An admin user able to manage the realm. **No `tide-realm-admin` role assignment is required** — that role is IGA-specific and not in play here. A standard Keycloak realm admin is sufficient.
|
|
50
|
+
- The asgard repo is already vendored under `sources/asgard-sdk/` in this pack — the .NET SDK is **consumed via `<ProjectReference>`**, not NuGet. INFERRED: NuGet publication is not yet verified for `0.1.0`.
|
|
51
|
+
- Your ASP.NET Core project ready to authenticate.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Files to Inspect
|
|
56
|
+
|
|
57
|
+
- `sources/asgard-sdk/aspnet/Tide.Asgard.AspNetCore/Tide.Asgard.AspNetCore.Example/Program.cs` — canonical wiring (note: README form and Example form **differ**, see Step 3).
|
|
58
|
+
- `sources/asgard-sdk/aspnet/Tide.Asgard.AspNetCore/Tide.Asgard.AspNetCore/Utils.cs` — `GetEd25519IssuerKey` extension.
|
|
59
|
+
- `sources/asgard-sdk/aspnet/Tide.Asgard.AspNetCore/Tide.Asgard.AspNetCore/ServiceCollectionExtensions.cs` — `AddTokenExchange` overloads.
|
|
60
|
+
- `sources/asgard-sdk/aspnet/Tide.Asgard.AspNetCore/Tide.Asgard.AspNetCore/TokenExchange/TokenExchangeService.cs` — exchange implementation; only the **Bearer** path works.
|
|
61
|
+
- `sources/asgard-sdk/README.md` — official .NET wiring guide (the README is exclusively about the .NET SDK; the TypeScript `asgard-tide` package is not documented there).
|
|
62
|
+
|
|
63
|
+
## Files to Edit
|
|
64
|
+
|
|
65
|
+
- `*.csproj` — add `<ProjectReference>` to the asgard projects.
|
|
66
|
+
- `Program.cs` — register authentication and (optionally) token exchange.
|
|
67
|
+
- `appsettings.json` — paste the **backend** client's adapter config under a `Keycloak` key.
|
|
68
|
+
- Controllers — add `[Authorize]`; inject `ITokenExchangeService` only where needed.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Step 1: Configure two Tidecloak clients
|
|
73
|
+
|
|
74
|
+
In your realm, create one **public** client for the browser SPA and one **confidential** client for the .NET backend. The audience mapper is mandatory — without it the backend rejects browser-issued tokens with 401.
|
|
75
|
+
|
|
76
|
+
| Client | Type | Settings | Why |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `browser-login-page` | Public | Set valid redirect URIs and web origins matching your SPA host (e.g. `http://localhost:3000`). | SPA login. |
|
|
79
|
+
| `backend` | Confidential | **Client authentication** ON. **Standard Token Exchange** ON (only if Step 5 needed). | Backend JWT validation + token exchange. |
|
|
80
|
+
|
|
81
|
+
Then add an **Audience mapper** to the browser client:
|
|
82
|
+
|
|
83
|
+
- In your realm -> Clients -> `browser-login-page` -> Client scopes -> `browser-login-page-dedicated` -> Add mapper -> By configuration -> Audience.
|
|
84
|
+
- Name: `backend-mapper`. Included Client Audience: `backend`. Save.
|
|
85
|
+
|
|
86
|
+
> If your realm uses Tide for user authentication, create your licence **before** creating any clients. VERIFIED (`sources/asgard-sdk/README.md` line 35).
|
|
87
|
+
|
|
88
|
+
> **Do NOT toggle IGA on** for this realm. The asgard .NET SDK does not work against IGA-enabled realms today — see Current Status above. Stop the canonical bootstrap order at the licence step; skip `toggle-iga`.
|
|
89
|
+
|
|
90
|
+
## Step 2: Add the asgard projects to your .NET solution
|
|
91
|
+
|
|
92
|
+
Until NuGet publication is verified, vendor the asgard repo into your workspace and reference the projects directly:
|
|
93
|
+
|
|
94
|
+
```xml
|
|
95
|
+
<!-- Your.Api.csproj -->
|
|
96
|
+
<ItemGroup>
|
|
97
|
+
<ProjectReference Include="..\asgard\aspnet\Tide.Asgard.AspNetCore\Tide.Asgard.AspNetCore\Tide.Asgard.AspNetCore.Authentication.csproj" />
|
|
98
|
+
<!-- Optional, only if you plan to register DPoP services manually -->
|
|
99
|
+
<ProjectReference Include="..\asgard\aspnet\Tide.Asgard.AspNetCore\Tide.Asgard.AspNetCore.DPoP\Tide.Asgard.AspNetCore.DPoP.csproj" />
|
|
100
|
+
</ItemGroup>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Transitive package dependencies that get pulled in:
|
|
104
|
+
|
|
105
|
+
| Package | Version | Source |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `Keycloak.AuthServices.Authentication` | 2.9.0 | Authentication.csproj |
|
|
108
|
+
| `Keycloak.AuthServices.Authorization` | 2.9.0 | Authentication.csproj |
|
|
109
|
+
| `Microsoft.AspNetCore.Authentication.JwtBearer` | 10.0.7 | Authentication.csproj |
|
|
110
|
+
| `BouncyCastle.Cryptography` | 2.6.2 | Core.csproj |
|
|
111
|
+
| `Microsoft.IdentityModel.Tokens` | 8.15.0 | Core.csproj |
|
|
112
|
+
|
|
113
|
+
## Step 3: Paste the backend adapter config into `appsettings.json`
|
|
114
|
+
|
|
115
|
+
Download the **backend** client's adapter config: realm -> Clients -> `backend` -> top-right **Action** -> **Download adapter config**. Paste under a `Keycloak` key. The section name is fixed — `Keycloak.AuthServices` reads it by default and `AddTokenExchange(IConfiguration)` hardcodes the same name (`ServiceCollectionExtensions.cs` line 117).
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"Logging": { "LogLevel": { "Default": "Information", "Keycloak.AuthServices": "Debug" } },
|
|
120
|
+
"AllowedHosts": "*",
|
|
121
|
+
"Keycloak": {
|
|
122
|
+
"realm": "<your-realm>",
|
|
123
|
+
"auth-server-url": "https://<your-tidecloak-host>",
|
|
124
|
+
"ssl-required": "external",
|
|
125
|
+
"resource": "backend",
|
|
126
|
+
"credentials": { "secret": "<from-credentials-tab>" },
|
|
127
|
+
"confidential-port": 0,
|
|
128
|
+
"jwk": {
|
|
129
|
+
"keys": [
|
|
130
|
+
{
|
|
131
|
+
"kid": "<from-realm-keys>",
|
|
132
|
+
"kty": "OKP",
|
|
133
|
+
"alg": "EdDSA",
|
|
134
|
+
"use": "sig",
|
|
135
|
+
"crv": "Ed25519",
|
|
136
|
+
"x": "<base64url-public-key>"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"homeOrkUrl": "https://<your-home-ork>"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> Do not commit real client secrets. The asgard example commits a demo secret in two places (`appsettings.json` and `README.md`), which the NuGet README inherits because `Authentication.csproj` packs the root README. **Use user secrets (`dotnet user-secrets`) or environment variables in real apps.** VERIFIED footgun.
|
|
146
|
+
|
|
147
|
+
## Step 4: Register authentication in `Program.cs`
|
|
148
|
+
|
|
149
|
+
Use the **README form**, not the Example form. The Example `Program.cs` has the `IssuerSigningKey` line commented out (`sources/asgard-sdk/.../Example/Program.cs:17`); copying it gives you a runtime that cannot validate Tidecloak's EdDSA tokens.
|
|
150
|
+
|
|
151
|
+
```csharp
|
|
152
|
+
using Keycloak.AuthServices.Authentication;
|
|
153
|
+
using Tide.Asgard.AspNetCore.Authentication;
|
|
154
|
+
|
|
155
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
156
|
+
builder.Services.AddControllers();
|
|
157
|
+
|
|
158
|
+
builder.Services
|
|
159
|
+
.AddKeycloakWebApiAuthentication(builder.Configuration, options =>
|
|
160
|
+
{
|
|
161
|
+
options.RequireHttpsMetadata = false; // set true in production
|
|
162
|
+
options.TokenValidationParameters.IssuerSigningKey =
|
|
163
|
+
Utils.GetEd25519IssuerKey(builder.Configuration);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
var app = builder.Build();
|
|
167
|
+
app.UseAuthentication();
|
|
168
|
+
app.UseAuthorization();
|
|
169
|
+
app.MapControllers();
|
|
170
|
+
app.Run();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
What this does:
|
|
174
|
+
|
|
175
|
+
- `AddKeycloakWebApiAuthentication` wires JWT Bearer validation against the `Keycloak` section.
|
|
176
|
+
- `Utils.GetEd25519IssuerKey` walks `Keycloak:jwk:keys[]` for `crv=Ed25519` and returns a `SecurityKey` backed by the EdDSA `SignatureProvider` in `Tide.Asgard.Core`. Tidecloak signs tokens with EdDSA — the stock Microsoft stack does not ship EdDSA, so this step is required.
|
|
177
|
+
- `GetEd25519IssuerKey` is an extension method on both `IConfiguration` (`Utils.cs:18`) and `IConfigurationSection` (`Utils.cs:25`). Either form works; the static call shown above is the most common.
|
|
178
|
+
|
|
179
|
+
## Step 5: (Optional) Wire Token Exchange
|
|
180
|
+
|
|
181
|
+
Only needed if your API calls another protected service on behalf of the caller. Register the service:
|
|
182
|
+
|
|
183
|
+
```csharp
|
|
184
|
+
builder.Services.AddTokenExchange(builder.Configuration); // reads the "Keycloak" section
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
To register multiple exchange clients in one app (e.g. one per downstream audience), use the `IConfigurationSection` overload and point each registration at a different section.
|
|
188
|
+
|
|
189
|
+
Inject `ITokenExchangeService` into a controller and call `ExchangeToken`:
|
|
190
|
+
|
|
191
|
+
```csharp
|
|
192
|
+
using Microsoft.AspNetCore.Authorization;
|
|
193
|
+
using Microsoft.AspNetCore.Mvc;
|
|
194
|
+
using Tide.Asgard.AspNetCore.Authentication.TokenExchange;
|
|
195
|
+
|
|
196
|
+
[Authorize]
|
|
197
|
+
[ApiController]
|
|
198
|
+
[Route("[controller]")]
|
|
199
|
+
public class HelloController(ITokenExchangeService exchange) : ControllerBase
|
|
200
|
+
{
|
|
201
|
+
[HttpGet]
|
|
202
|
+
public async Task<IActionResult> Get()
|
|
203
|
+
{
|
|
204
|
+
var token = await exchange.ExchangeToken(
|
|
205
|
+
HttpContext.Request.Headers,
|
|
206
|
+
requestingClientId: "backend",
|
|
207
|
+
requestedAudience: "downstream-service"); // must exist as a realm client
|
|
208
|
+
return Ok(token);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Behavioral notes:
|
|
214
|
+
|
|
215
|
+
- The exchange posts `grant_type=urn:ietf:params:oauth:grant-type:token-exchange` to `protocol/openid-connect/token` and returns the new `access_token` (`TokenExchangeService.cs:42-85`).
|
|
216
|
+
- The HTTP client is registered as a **singleton with the backend client secret baked into the Basic-auth header** (`ServiceCollectionExtensions.cs:91-97`). Rotating the secret requires an app restart.
|
|
217
|
+
- `requestedAudience` must be a realm client. The asgard Example uses Keycloak's built-in `"account"` client as a demo; replace with a real audience.
|
|
218
|
+
|
|
219
|
+
## Step 6: Protect endpoints
|
|
220
|
+
|
|
221
|
+
Standard ASP.NET Core authorization. Nothing Tide-specific.
|
|
222
|
+
|
|
223
|
+
```csharp
|
|
224
|
+
[Authorize]
|
|
225
|
+
[ApiController]
|
|
226
|
+
[Route("[controller]")]
|
|
227
|
+
public class SecretController : ControllerBase
|
|
228
|
+
{
|
|
229
|
+
[HttpGet]
|
|
230
|
+
public IActionResult Get() => Ok($"Hello, {User.Identity?.Name}");
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
For role-based authorization, use `Keycloak.AuthServices.Authorization` policies — the `Authorization` package is referenced transitively.
|
|
235
|
+
|
|
236
|
+
## Step 7: SPA side (if you control it)
|
|
237
|
+
|
|
238
|
+
Your SPA uses `@tidecloak/js` with the **public** client's adapter config. Reference pattern (`sources/asgard-sdk/.../Example/ClientApp/src/main.js`):
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
import { TideCloak } from "@tidecloak/js";
|
|
242
|
+
const kc = new TideCloak("keycloak.json");
|
|
243
|
+
await kc.init({ onLoad: "login-required", checkLoginIframe: false });
|
|
244
|
+
await kc.secureFetch(`${origin}/Secret`, {
|
|
245
|
+
headers: { Authorization: `Bearer ${kc.token}` },
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The audience mapper from Step 1 is what lets the .NET backend accept tokens issued for `browser-login-page`. If `aud` does not include `backend` you will see 401.
|
|
250
|
+
|
|
251
|
+
**Do not enable `useDPoP`** in `kc.init({...})` at this point. The .NET token-exchange path crashes on DPoP-tagged requests — see [Common Failures](#common-failures).
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Verification
|
|
256
|
+
|
|
257
|
+
- [ ] `Tide.Asgard.AspNetCore.Authentication.csproj` is referenced from your csproj.
|
|
258
|
+
- [ ] `appsettings.json` has a `Keycloak` section with `realm`, `auth-server-url`, `resource`, `credentials.secret`, and `jwk.keys[]` containing an Ed25519 key.
|
|
259
|
+
- [ ] `Program.cs` calls `AddKeycloakWebApiAuthentication` **with** `options.TokenValidationParameters.IssuerSigningKey = Utils.GetEd25519IssuerKey(...)`.
|
|
260
|
+
- [ ] An unauthenticated request to a `[Authorize]` endpoint returns 401.
|
|
261
|
+
- [ ] A request with a valid `Authorization: Bearer <token>` from `browser-login-page` (containing `backend` in `aud`) succeeds.
|
|
262
|
+
- [ ] If Step 5 is wired: `ITokenExchangeService.ExchangeToken(...)` returns a non-empty access token for the configured audience.
|
|
263
|
+
- [ ] Logs show `Keycloak.AuthServices` at Debug level emitting validation traces.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Common Failures
|
|
268
|
+
|
|
269
|
+
| Symptom | Cause | Repair |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| 401 with `IDX10503: Signature validation failed. Token does not have a kid` or `Unable to match keys` | Step 4's `IssuerSigningKey = Utils.GetEd25519IssuerKey(...)` line missing or commented (matches the Example, not the README) | Add the line back. See `sources/asgard-sdk/README.md:129`. |
|
|
272
|
+
| 401 with `Audience validation failed` on browser-issued tokens | Audience mapper missing from `browser-login-page` | Step 1 — add `backend-mapper` to the `browser-login-page-dedicated` client scope. |
|
|
273
|
+
| `NotImplementedException` thrown from `ExchangeToken` | The SPA enabled DPoP. `TokenExchangeService.cs:28-35` routes DPoP-tagged requests to `ExchangeDPoPToken`, which is a stub at line 93 | Disable `useDPoP` on the SPA (`kc.init`) until the SDK ships a DPoP-aware exchange path. |
|
|
274
|
+
| `NotImplementedException` thrown from a "Doken" auth header | `ExchangeTideDoken` is a stub at `TokenExchangeService.cs:132` | Do not use the Doken auth scheme with this SDK today. |
|
|
275
|
+
| Backend ignores DPoP proof on inbound requests | `.WithDPoP(...)` is commented out at `KeycloakWebApiAuthenticationBuilderExtensions.cs:33-68`; `DPoPProofValidationService` exists but is never registered | If you need DPoP today, register `DPoPProofValidationService` and the `MessageReceivedHandler` / `TokenValidationHandler` / `ChallengeHandler` event handlers manually. Or wait for the SDK to ship `.WithDPoP`. |
|
|
276
|
+
| 404 on `/` when running the asgard Example cold | The SPA at `ClientApp/` is **not built by MSBuild**. Vite outputs to `wwwroot/` but `Example.csproj` has no `<Target>` running `npm run build` | Run `npm install && npm run build` in `ClientApp/` before `dotnet run`. |
|
|
277
|
+
| `@tidecloak/js` cannot be resolved when running the asgard Example | `ClientApp/package.json` references `@tidecloak/js` from filesystem path `~/tidecloak-js/packages/tidecloak-js`, not npm | Clone `github.com/tide-foundation/tidecloak-js` to `~/tidecloak-js/` or update the reference to an npm version. |
|
|
278
|
+
| `BadImageFormat`, target framework mismatch | Project targets a pre-net10.0 framework | Bump `<TargetFramework>net10.0</TargetFramework>` and install the .NET 10 SDK. |
|
|
279
|
+
| Realm secret rotated but exchanges still use the old secret | The exchange `HttpClient` is a singleton with Basic-auth baked in at registration time (`ServiceCollectionExtensions.cs:91-97`) | Restart the app after rotation. ASSUMED: there is no hot-reload path. |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Anti-patterns and Do Not Do This
|
|
284
|
+
|
|
285
|
+
- **Do not copy the Example `Program.cs` verbatim.** The Example has `IssuerSigningKey` commented out — JWT validation will silently fail on EdDSA tokens. Use the README form.
|
|
286
|
+
- **Do not commit `credentials.secret`** to source control. The asgard example does this twice for demo purposes (`appsettings.json:20`, `README.md:91`). Use `dotnet user-secrets` for development and environment variables / a secrets manager for production.
|
|
287
|
+
- **Do not enable browser DPoP today.** `useDPoP` in the SPA causes every Token Exchange call to throw `NotImplementedException` (see Common Failures).
|
|
288
|
+
- **Do not point `requestedAudience` at the wrong client.** It must exist as a realm client with Standard Token Exchange enabled. `"account"` works in demos because it is Keycloak's built-in client.
|
|
289
|
+
- **Do not register `AddTokenExchange(IConfiguration)` for multi-client scenarios.** Use the `IConfigurationSection` overload — the `IConfiguration` form hardcodes the `"Keycloak"` section name (`ServiceCollectionExtensions.cs:117`).
|
|
290
|
+
- **Do not rely on the commented-out helpers.** `UseTidecloakDashboard`, `UseTideSecuredDPoP`, `AddAutoClientCeritificationToDashboard`, `.WithDPoP` — all four are commented out at HEAD. The public type signatures (`TidecloakDashboardOptions`, `MTLSOptions`, `ClientCertificationOptions`) ship but nothing wires them.
|
|
291
|
+
- **Do not assume the .NET SDK validates Tide Policies / Forseti contracts.** Policy enforcement happens on ORKs (C# Forseti contracts, see [custom-contracts.md](../canon/custom-contracts.md)). The .NET SDK only handles JWT auth and token exchange.
|
|
292
|
+
- **Do not treat folder names as canonical.** The source tree contains typos that are reachable via `using` statements: `ClientCeritifcation/` (should be `ClientCertification`), `MessageRecievedHandler.cs` (should be `Received`). Grep for the wrong spelling or you will miss them.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Repair Path
|
|
297
|
+
|
|
298
|
+
If you have an existing ASP.NET Core API on TideCloak that is misbehaving:
|
|
299
|
+
|
|
300
|
+
1. **JWT validation failures** — confirm `IssuerSigningKey = Utils.GetEd25519IssuerKey(...)` is present in `Program.cs`. The Microsoft default JWT handler does not ship EdDSA.
|
|
301
|
+
2. **401 with audience mismatch** — confirm the SPA's client has an audience mapper to the backend client.
|
|
302
|
+
3. **Token exchange throws** — confirm no DPoP / Doken auth header is being sent. Today only Bearer works.
|
|
303
|
+
4. **Inbound DPoP** — confirm whether you actually need it. If yes, you are off the supported path and must register DPoP services manually (see Common Failures row). If no, ignore the `Tide.Asgard.AspNetCore.DPoP` project entirely.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Cross-references
|
|
308
|
+
|
|
309
|
+
- [add-auth-nextjs-fresh.md](add-auth-nextjs-fresh.md) — Next.js equivalent (different SDK).
|
|
310
|
+
- [protect-api-nextjs.md](protect-api-nextjs.md) / [verify-jwt-server-side.md](verify-jwt-server-side.md) — Node-side JWT verification path.
|
|
311
|
+
- [Step 5: Wire Token Exchange](#step-5-optional-wire-token-exchange) — RFC 8693 token exchange is the shipped path for .NET server-to-server delegation. (Browser-driven server-side delegation is an asgard feature — the `asgard-tide` Node SDK, the same one keylessh's server-delegation uses — and is unmerged/experimental, not on main, so there is no standalone server-delegation playbook.)
|
|
312
|
+
- [custom-contracts.md](../canon/custom-contracts.md) — Forseti contracts; out of scope for this playbook but relevant if your .NET API is downstream of policy-governed signing.
|
|
313
|
+
- [framework-matrix.md](../canon/framework-matrix.md#net--aspnet-core-via-asgard) — concise framework entry for .NET via asgard.
|
|
@@ -411,10 +411,12 @@ If route protection broken:
|
|
|
411
411
|
|
|
412
412
|
1. **Verify auth works**:
|
|
413
413
|
```typescript
|
|
414
|
-
// Add to protected page
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
414
|
+
// Add to protected page.
|
|
415
|
+
// Note: useTideCloak() has no `user` object — read claims via
|
|
416
|
+
// getValueFromIdToken / getValueFromToken.
|
|
417
|
+
const { authenticated, getValueFromIdToken } = useTideCloak();
|
|
418
|
+
console.log('Auth:', authenticated, 'User:', getValueFromIdToken('preferred_username'));
|
|
419
|
+
// Should log: Auth: true, User: "<username>"
|
|
418
420
|
```
|
|
419
421
|
|
|
420
422
|
2. **Check provider wraps app**:
|
|
@@ -459,10 +461,10 @@ export async function GET() {
|
|
|
459
461
|
|
|
460
462
|
---
|
|
461
463
|
|
|
462
|
-
### ❌ Do Not
|
|
464
|
+
### ❌ Do Not Hand-Roll a Cookie-Presence Check in proxy.ts / middleware.ts
|
|
463
465
|
|
|
464
466
|
```typescript
|
|
465
|
-
// ❌ WRONG:
|
|
467
|
+
// ❌ WRONG: hand-written proxy/middleware that only checks a cookie exists
|
|
466
468
|
// proxy.ts (or middleware.ts in legacy projects)
|
|
467
469
|
import { NextResponse } from 'next/server';
|
|
468
470
|
|
|
@@ -474,14 +476,26 @@ export function middleware(req) {
|
|
|
474
476
|
}
|
|
475
477
|
```
|
|
476
478
|
|
|
477
|
-
**Why**:
|
|
478
|
-
1.
|
|
479
|
-
2.
|
|
479
|
+
**Why this hand-rolled version is wrong**:
|
|
480
|
+
1. Checks cookie presence, not JWT validity
|
|
481
|
+
2. No signature verification against the adapter's embedded `jwk`
|
|
480
482
|
3. Not real authorization
|
|
481
483
|
|
|
484
|
+
**Use the shipped helper instead.** `@tidecloak/nextjs/server` exports `createTideCloakProxy` (Next.js 16+, Node runtime) and `createTideCloakMiddleware` (Edge, legacy), which DO verify the token via `verifyTideCloakToken`:
|
|
485
|
+
```typescript
|
|
486
|
+
// proxy.ts (Next.js 16+) — createTideCloakMiddleware for middleware.ts on ≤15
|
|
487
|
+
import { createTideCloakProxy } from '@tidecloak/nextjs/server';
|
|
488
|
+
import tcConfig from './data/tidecloak.json';
|
|
489
|
+
|
|
490
|
+
export default createTideCloakProxy(tcConfig);
|
|
491
|
+
export const config = { matcher: ['/dashboard/:path*', '/admin/:path*'] };
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Even with a verifying proxy, keep server-side JWT verification inside API routes as the authoritative enforcement point (route interception governs page navigation, not direct API calls).
|
|
495
|
+
|
|
482
496
|
**Version rule**: Next.js 16+ uses `proxy.ts`. Next.js 15 and earlier use `middleware.ts`. Check the installed version before creating or looking for this file. See [canon/framework-matrix.md](../canon/framework-matrix.md#request-interception--route-protection-ui-only).
|
|
483
497
|
|
|
484
|
-
**Correct**: Client-side route guards for UX, server-side JWT verification for security.
|
|
498
|
+
**Correct**: Client-side route guards (or `createTideCloakProxy`) for UX, server-side JWT verification for security.
|
|
485
499
|
|
|
486
500
|
---
|
|
487
501
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Provision Hosted TideCloak via Skycloak
|
|
2
|
+
|
|
3
|
+
Provision a fully-managed TideCloak instance in Skycloak's cloud instead of self-hosting, then bootstrap the Tide realm on top of it. This is the hosted alternative to `deploy-tidecloak-docker`.
|
|
4
|
+
|
|
5
|
+
Read `canon/hosting-options.md` first — it covers the self-host vs hosted decision, the trust model, and the honest caveats you must surface to the operator.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
- The team does not want to run auth infrastructure (containers, DB, upgrades, TLS, backups).
|
|
12
|
+
- You want a managed TideCloak reachable at a stable URL with no ops burden.
|
|
13
|
+
- You are prototyping and want an instance without local Docker.
|
|
14
|
+
|
|
15
|
+
**Do not use** if:
|
|
16
|
+
- The deployment must be air-gapped or fully self-controlled → `deploy-tidecloak-docker`.
|
|
17
|
+
- You already have a running TideCloak → go straight to realm bootstrap / integration.
|
|
18
|
+
- The operator has not confirmed a Skycloak account and API key exist (this playbook cannot create the account).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- A Skycloak account with a workspace (dashboard at `skycloak.io`).
|
|
25
|
+
- A Skycloak **API key** with scope `clusters:write` and `clusters:credentials:read`, created in Dashboard → Workspace → API keys. Shown once — capture it securely.
|
|
26
|
+
- `curl` and `jq`.
|
|
27
|
+
- Plan level sufficient for your region/size (non-US regions and larger sizes need Developer plan or higher; a `402 Payment Required` means the action isn't on the current plan).
|
|
28
|
+
|
|
29
|
+
**Secret handling (AP-HOST-3):** The API key and any cluster automation-client secret are operator/bootstrap secrets. Keep them in the shell/CI environment. Never write them into application code, the repo, or `tidecloak.json`. Same rule as master admin credentials (AP-41).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Overview of the flow
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
1. Create a TideCloak cluster (Skycloak API) → cluster id
|
|
37
|
+
2. Poll until status = available (Skycloak API) → cluster URL
|
|
38
|
+
3. Fetch automation-client creds (Skycloak API) → admin token source
|
|
39
|
+
4. Bootstrap the Tide realm (TideCloak admin API) → adapter JSON
|
|
40
|
+
5. Wire the app (unchanged) → same as self-hosted
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Steps 1–3 are Skycloak-specific and covered here. Step 4 is the **same Tide-realm bootstrap** as the self-host path — reuse `bootstrap-realm-from-template` / the `deploy-tidecloak-docker` init sequence, pointed at the hosted URL with a token from the automation client instead of master admin creds. Step 5 is identical to any Tide app (`add-auth-nextjs-fresh` etc.).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Step 1: Create a TideCloak cluster
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export SKYCLOAK_API_KEY="<your-key>"
|
|
51
|
+
API="https://api.skycloak.io"
|
|
52
|
+
VER="2026-06-01.beta"
|
|
53
|
+
|
|
54
|
+
# NOTE: exact request-body field names are INFERRED from the docs — if this 422s,
|
|
55
|
+
# inspect the RFC 9457 error body (it lists the offending field) and adjust.
|
|
56
|
+
curl -s -X POST "$API/clusters" \
|
|
57
|
+
-H "API-Key: $SKYCLOAK_API_KEY" \
|
|
58
|
+
-H "API-Version: $VER" \
|
|
59
|
+
-H "Content-Type: application/json" \
|
|
60
|
+
-d '{
|
|
61
|
+
"identityPlatform": "TideCloak",
|
|
62
|
+
"name": "myapp-auth",
|
|
63
|
+
"size": "Small",
|
|
64
|
+
"region": "us-east"
|
|
65
|
+
}' | tee cluster-create.json | jq .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **`identityPlatform: "TideCloak"`** is the field that makes this a Tide broker rather than plain Keycloak. Do not omit it.
|
|
69
|
+
- `size`: `Small` (DEV), `Medium` (STAGING), `Large` (PROD).
|
|
70
|
+
- Capture the returned cluster **id**: `CLUSTER_ID=$(jq -r '.id' cluster-create.json)`.
|
|
71
|
+
- Creation is **asynchronous** — the response comes back before the cluster is ready.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Step 2: Poll until available
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
for i in $(seq 1 40); do
|
|
79
|
+
STATUS=$(curl -s "$API/clusters/$CLUSTER_ID" \
|
|
80
|
+
-H "API-Key: $SKYCLOAK_API_KEY" -H "API-Version: $VER" | jq -r '.status')
|
|
81
|
+
echo "attempt $i: $STATUS"
|
|
82
|
+
case "$STATUS" in
|
|
83
|
+
available) echo "ready"; break ;;
|
|
84
|
+
failed) echo "provisioning FAILED — check the Skycloak dashboard"; exit 1 ;;
|
|
85
|
+
esac
|
|
86
|
+
sleep 15
|
|
87
|
+
done
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Provisioning is typically 2–4 minutes; Skycloak also emails on completion. Do not proceed to bootstrap until status is `available`. The cluster is reachable at `https://<cluster-id>.app.skycloak.io`:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
TIDECLOAK_URL="https://${CLUSTER_ID}.app.skycloak.io"
|
|
94
|
+
curl -s -f "$TIDECLOAK_URL" > /dev/null && echo "TideCloak reachable" || echo "not reachable yet"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Step 3: Get an admin token (no master password exists)
|
|
100
|
+
|
|
101
|
+
Skycloak does **not** issue a Keycloak admin username/password. Two ways to reach the admin API:
|
|
102
|
+
|
|
103
|
+
**A. Admin Console SSO (interactive)** — for a human clicking through: open the cluster's "Go to Console" from the Skycloak dashboard, authenticated by your Skycloak account. Use this to eyeball the instance; it does not give a scriptable token.
|
|
104
|
+
|
|
105
|
+
**B. Automation client (scriptable)** — each cluster has a confidential OAuth2 client `skycloak-automation-<cluster-id>` in the `master` realm for programmatic admin access. Fetch its credentials, then use client-credentials to mint admin tokens:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Field names of the credentials response are INFERRED — inspect the actual JSON.
|
|
109
|
+
curl -s "$API/clusters/$CLUSTER_ID/credentials" \
|
|
110
|
+
-H "API-Key: $SKYCLOAK_API_KEY" -H "API-Version: $VER" | tee cluster-creds.json | jq .
|
|
111
|
+
|
|
112
|
+
CLIENT_ID=$(jq -r '.clientId // .automationClientId // empty' cluster-creds.json)
|
|
113
|
+
CLIENT_SECRET=$(jq -r '.clientSecret // .secret // empty' cluster-creds.json)
|
|
114
|
+
|
|
115
|
+
# Mint an admin token against the hosted instance's master realm:
|
|
116
|
+
get_token() {
|
|
117
|
+
curl -s -X POST "$TIDECLOAK_URL/realms/master/protocol/openid-connect/token" \
|
|
118
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
119
|
+
-d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
|
|
120
|
+
| jq -r '.access_token'
|
|
121
|
+
}
|
|
122
|
+
TOKEN="$(get_token)"
|
|
123
|
+
[ -n "$TOKEN" ] && [ "$TOKEN" != "null" ] && echo "admin token OK" || echo "token failed — check creds/scopes"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This `get_token` replaces the master-admin `get_token` in the self-host init script. Everything downstream (realm create, `setUpTideRealm`, IGA toggle, change-request authorize/commit, adapter export) is the same — just pointed at `$TIDECLOAK_URL` with this token.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Step 4: Bootstrap the Tide realm
|
|
131
|
+
|
|
132
|
+
Run the standard Tide-realm bootstrap against the hosted instance:
|
|
133
|
+
|
|
134
|
+
1. `bootstrap-realm-from-template` — create the realm from the `realm.json` template (identical template to self-host; see `deploy-tidecloak-docker` Step 2), then `setUpTideRealm` + enable IGA.
|
|
135
|
+
2. `initialize-admin-and-link-account` — create the admin user, assign `tide-realm-admin`, generate the Tide-account link, approve the change requests, export the adapter JSON.
|
|
136
|
+
|
|
137
|
+
Use the `get_token()` from Step 3 (automation client) everywhere the self-host script uses the master-admin token. Point `TIDECLOAK_URL` at `https://<cluster-id>.app.skycloak.io`.
|
|
138
|
+
|
|
139
|
+
> **VERIFY THIS ON THE LIVE INSTANCE (GAP-066).** The pack cannot yet confirm that a Skycloak-hosted TideCloak exposes the full Tide vendor surface (`setUpTideRealm`, `toggle-iga`, the change-request API, adapter export with `jwk`/`vendorId`/`homeOrkUrl`) or how Tide **licensing** is handled in the hosted context. Before promising a turnkey result:
|
|
140
|
+
> - Probe `POST .../vendorResources/setUpTideRealm` and check for a 2xx + licensing JSON.
|
|
141
|
+
> - After bootstrap, confirm the exported adapter JSON contains `jwk`, `vendorId`, `homeOrkUrl` (I-05, I-13).
|
|
142
|
+
> - If those endpoints 404 or the adapter lacks Tide fields, **stop** — Skycloak is hosting the broker but Tide-realm provisioning/licensing needs the partner's Tide-specific path. Report this to the operator and raise with the Tide/Skycloak teams. Do not fabricate the adapter JSON.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Step 5: Wire the app
|
|
147
|
+
|
|
148
|
+
Identical to any Tide app — the hosting choice does not change integration. Adapter JSON goes to `data/tidecloak.json` (Next.js) or `public/tidecloak.json` (React/Vite), with `auth-server-url` pointing at the hosted URL. Continue with `add-auth-nextjs-fresh` / `add-auth-nextjs-existing`, then route/API protection.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Verification Checklist
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Cluster available
|
|
156
|
+
curl -s "$API/clusters/$CLUSTER_ID" -H "API-Key: $SKYCLOAK_API_KEY" -H "API-Version: $VER" | jq '.status'
|
|
157
|
+
# → "available"
|
|
158
|
+
|
|
159
|
+
# Hosted TideCloak reachable
|
|
160
|
+
curl -s -f "https://${CLUSTER_ID}.app.skycloak.io" > /dev/null && echo reachable
|
|
161
|
+
|
|
162
|
+
# Admin token from automation client works
|
|
163
|
+
[ -n "$(get_token)" ] && echo "token OK"
|
|
164
|
+
|
|
165
|
+
# After bootstrap: adapter has Tide extensions (the real turnkey test — GAP-066)
|
|
166
|
+
jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
|
|
167
|
+
# → true (if false: GAP-066 — hosted Tide vendor surface not confirmed)
|
|
168
|
+
|
|
169
|
+
# IGA enabled on the hosted realm
|
|
170
|
+
curl -s "https://${CLUSTER_ID}.app.skycloak.io/admin/realms/myapp" \
|
|
171
|
+
-H "Authorization: Bearer $(get_token)" | jq '.attributes.isIGAEnabled'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Common Failures
|
|
177
|
+
|
|
178
|
+
### `402 Payment Required` on create
|
|
179
|
+
The chosen region/size isn't available on the current plan (e.g. a non-US region on a trial workspace). Use a US region / `Small` size, or upgrade the plan. The RFC 9457 body's `detail` states the constraint.
|
|
180
|
+
|
|
181
|
+
### `403` "does not have the required scope"
|
|
182
|
+
The API key lacks `clusters:write` (create) or `clusters:credentials:read` (Step 3). Create a key with the right scopes; note write includes read.
|
|
183
|
+
|
|
184
|
+
### Missing `API-Version` header (AP-HOST-4)
|
|
185
|
+
Every Skycloak call needs `-H "API-Version: 2026-06-01.beta"`. Omitting it fails the request.
|
|
186
|
+
|
|
187
|
+
### `422` on create with a field complaint
|
|
188
|
+
The request-body field names here are INFERRED from the docs. Read the `errors[]` array in the RFC 9457 response — it names the offending `field` — and adjust (`identityPlatform`/`size`/`region` spellings, enum values).
|
|
189
|
+
|
|
190
|
+
### Cluster stuck in `provisioning` / went `failed`
|
|
191
|
+
Provisioning is async and occasionally fails server-side. Check the Skycloak dashboard for the cluster's error detail; recreate if `failed`. Do not bootstrap against a non-`available` cluster.
|
|
192
|
+
|
|
193
|
+
### Adapter JSON missing Tide fields after bootstrap (GAP-066)
|
|
194
|
+
The hosted instance may not expose the Tide vendor surface, or licensing wasn't provisioned. Do not fall back to `createRemoteJWKSet` or hand-build the adapter (I-04). Stop and escalate — this is the open question the hosted path depends on.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Anti-Patterns
|
|
199
|
+
|
|
200
|
+
- **AP-HOST-2** — Telling the user the hosted Tide path is fully turnkey before GAP-066 is confirmed. Cluster provisioning is verified; the Tide-realm bootstrap on top is not.
|
|
201
|
+
- **AP-HOST-3** — Putting `SKYCLOAK_API_KEY` or the `skycloak-automation-*` secret in app code, `tidecloak.json`, or the repo. Bootstrap secrets only.
|
|
202
|
+
- **AP-HOST-4** — Omitting the `API-Version` header.
|
|
203
|
+
- **Do not** fabricate `tidecloak.json` if the hosted vendor endpoints are absent. A missing adapter is a provisioning problem to escalate, not a file to invent (I-05, I-13).
|
|
204
|
+
- **Do not** present hosting as either a security downgrade or a magic upgrade — state the real trust model from `canon/hosting-options.md` (availability + metadata + Tideless-IGA caveat).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## References
|
|
209
|
+
|
|
210
|
+
- `canon/hosting-options.md` — decision, trust model, Skycloak API reference, GAP-066
|
|
211
|
+
- Skycloak docs: `https://skycloak.io/docs/api/` (API-Version `2026-06-01.beta`)
|
|
212
|
+
- `deploy-tidecloak-docker` — the self-host equivalent and the shared realm-bootstrap sequence
|
|
213
|
+
- `bootstrap-realm-from-template`, `initialize-admin-and-link-account` — the Tide-realm steps reused in Step 4
|