Skip to content

Commit 0e609d0

Browse files
committed
Add scope-gated custom claims support for UserInfo endpoint
- Add CoreIdentUserInfoOptions for configuring custom claims scope and allowlist - Update UserInfo endpoint to include custom claims only when configured scope is granted - Add XML documentation comments for CoreIdentUserInfoOptions and CoreIdentClient methods - Update client GetUserAsync to use validated ID token as base and enrich with UserInfo claims - Add GetValidatedIdTokenUserAsync helper for debugging ID token claims separately - Update client samples to show claim sources distinctly (ID token vs UserInfo vs access token) - Add server and client sample projects with custom_claims scope configuration - Fix duplicate DumpClaims function in client samples - Add comprehensive documentation for custom claims testing
1 parent cd42a93 commit 0e609d0

File tree

19 files changed

+1523
-37
lines changed

19 files changed

+1523
-37
lines changed

CoreIdent.sln

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.Templates.Tests",
4343
EndProject
4444
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CoreIdent.FSharp.Sample", "samples\CoreIdent.FSharp.Sample\CoreIdent.FSharp.Sample.fsproj", "{A90B9D37-39B3-4AE4-8AE1-52E5A02CB200}"
4545
EndProject
46+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
47+
EndProject
48+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.Client.Samples", "samples\CoreIdent.Client.Samples\CoreIdent.Client.Samples.csproj", "{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}"
49+
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.Server.Samples", "samples\CoreIdent.Server.Samples\CoreIdent.Server.Samples.csproj", "{3358BBBC-5B99-415D-8113-FF763CC9DDA5}"
51+
EndProject
4652
Global
4753
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4854
Debug|Any CPU = Debug|Any CPU
@@ -269,28 +275,54 @@ Global
269275
{A90B9D37-39B3-4AE4-8AE1-52E5A02CB200}.Release|x64.Build.0 = Release|Any CPU
270276
{A90B9D37-39B3-4AE4-8AE1-52E5A02CB200}.Release|x86.ActiveCfg = Release|Any CPU
271277
{A90B9D37-39B3-4AE4-8AE1-52E5A02CB200}.Release|x86.Build.0 = Release|Any CPU
278+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
279+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Debug|Any CPU.Build.0 = Debug|Any CPU
280+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Debug|x64.ActiveCfg = Debug|Any CPU
281+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Debug|x64.Build.0 = Debug|Any CPU
282+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Debug|x86.ActiveCfg = Debug|Any CPU
283+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Debug|x86.Build.0 = Debug|Any CPU
284+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Release|Any CPU.ActiveCfg = Release|Any CPU
285+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Release|Any CPU.Build.0 = Release|Any CPU
286+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Release|x64.ActiveCfg = Release|Any CPU
287+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Release|x64.Build.0 = Release|Any CPU
288+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Release|x86.ActiveCfg = Release|Any CPU
289+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F}.Release|x86.Build.0 = Release|Any CPU
290+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
291+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
292+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Debug|x64.ActiveCfg = Debug|Any CPU
293+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Debug|x64.Build.0 = Debug|Any CPU
294+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Debug|x86.ActiveCfg = Debug|Any CPU
295+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Debug|x86.Build.0 = Debug|Any CPU
296+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
297+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Release|Any CPU.Build.0 = Release|Any CPU
298+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Release|x64.ActiveCfg = Release|Any CPU
299+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Release|x64.Build.0 = Release|Any CPU
300+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Release|x86.ActiveCfg = Release|Any CPU
301+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5}.Release|x86.Build.0 = Release|Any CPU
272302
EndGlobalSection
273303
GlobalSection(SolutionProperties) = preSolution
274304
HideSolutionNode = FALSE
275305
EndGlobalSection
276306
GlobalSection(NestedProjects) = preSolution
277307
{13763257-46A9-43D3-B447-01E9B886AB99} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
308+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
278309
{7DAD0A0D-9B1B-4C2B-A758-9E5D5C1C4B8A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
279310
{A8E0F5F1-86B0-4E6E-9B8D-0C2C15FA1C9D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
280311
{C1A4B06F-5F7C-4D18-9A0B-2E8A0DAB01D6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
281312
{629C0C37-9B6B-4E43-B8F5-C38F6CC223A5} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
282313
{2C6E6D31-7DA0-4B50-8A71-55AB7E847E0D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
283314
{EF48235B-6889-4C3C-8E58-C8A1FF2005ED} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
284315
{8FAD8D9A-6D73-4C41-9F12-321A6E2E8C6D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
316+
{B9D9E1D1-0A77-4A4C-9E4E-2F22CE5B1D40} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
317+
{1E2D7B4F-45D8-4E18-9D1E-8DAE0A41E5F0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
285318
{FDB14E50-0569-4EEA-B1DC-931025401D99} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
286319
{A2F2D5AE-0C83-4B62-8D25-6D4B1BC8E2E5} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
287320
{4D7B54B2-3E59-4BC4-9A87-8DDC6D68E6E0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
288321
{B11D0B6B-6E2E-4A49-8F1A-5D05C82B4E67} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
289322
{CE3F11D3-060F-4F4C-8A40-5A67AFDAD1D0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
290323
{D5A2C0E8-2B2F-4A54-9EA1-6E5D3C59D6E7} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
291324
{A90B9D37-39B3-4AE4-8AE1-52E5A02CB200} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
292-
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
293-
{B9D9E1D1-0A77-4A4C-9E4E-2F22CE5B1D40} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
294-
{1E2D7B4F-45D8-4E18-9D1E-8DAE0A41E5F0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
325+
{08E5F2CA-FB60-4E61-B0B3-ADBD6743C19F} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
326+
{3358BBBC-5B99-415D-8113-FF763CC9DDA5} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
295327
EndGlobalSection
296328
EndGlobal
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Net;
2+
using CoreIdent.Client;
3+
4+
namespace CoreIdent.Client.Samples.BrowserLaunchers;
5+
6+
/// <summary>
7+
/// A test-only browser launcher that simulates an interactive login by calling the authorize URL
8+
/// with a test-header authenticated request and returning the redirect Location.
9+
/// </summary>
10+
public sealed class TestHeaderBrowserLauncher : IBrowserLauncher, IDisposable
11+
{
12+
private readonly HttpClient _http;
13+
private readonly bool _ownsHttp;
14+
private readonly string _userId;
15+
private readonly string? _email;
16+
17+
public TestHeaderBrowserLauncher(HttpClient http, string userId, string? email)
18+
{
19+
ArgumentNullException.ThrowIfNull(http);
20+
ArgumentException.ThrowIfNullOrWhiteSpace(userId);
21+
22+
_ownsHttp = false;
23+
_http = http;
24+
_userId = userId;
25+
_email = string.IsNullOrWhiteSpace(email) ? null : email;
26+
}
27+
28+
public TestHeaderBrowserLauncher(HttpMessageHandler handler, Uri baseAddress, string userId, string? email)
29+
{
30+
ArgumentNullException.ThrowIfNull(handler);
31+
ArgumentNullException.ThrowIfNull(baseAddress);
32+
ArgumentException.ThrowIfNullOrWhiteSpace(userId);
33+
34+
_ownsHttp = true;
35+
_http = new HttpClient(handler) { BaseAddress = baseAddress };
36+
_userId = userId;
37+
_email = string.IsNullOrWhiteSpace(email) ? null : email;
38+
}
39+
40+
public async Task<BrowserResult> LaunchAsync(string url, string redirectUri, CancellationToken ct = default)
41+
{
42+
ArgumentException.ThrowIfNullOrWhiteSpace(url);
43+
ArgumentException.ThrowIfNullOrWhiteSpace(redirectUri);
44+
45+
using var req = new HttpRequestMessage(HttpMethod.Get, url);
46+
47+
// CoreIdent sample host uses this auth scheme for /auth/authorize.
48+
req.Headers.TryAddWithoutValidation("X-Test-User-Id", _userId);
49+
if (_email is not null)
50+
{
51+
req.Headers.TryAddWithoutValidation("X-Test-User-Email", _email);
52+
}
53+
54+
using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
55+
56+
if (resp.StatusCode is HttpStatusCode.Found or HttpStatusCode.SeeOther)
57+
{
58+
var location = resp.Headers.Location?.ToString();
59+
if (string.IsNullOrWhiteSpace(location))
60+
{
61+
return BrowserResult.Fail("invalid_response", "Authorize response did not include a Location header.");
62+
}
63+
64+
// Return the URL (which should be the redirect_uri with code + state).
65+
return BrowserResult.Success(location);
66+
}
67+
68+
var body = string.Empty;
69+
try
70+
{
71+
body = await resp.Content.ReadAsStringAsync(ct);
72+
}
73+
catch
74+
{
75+
// ignore
76+
}
77+
78+
return BrowserResult.Fail(
79+
"authorize_failed",
80+
$"Authorize request failed. Status={(int)resp.StatusCode} {resp.ReasonPhrase}. Body={body}");
81+
}
82+
83+
public void Dispose()
84+
{
85+
if (_ownsHttp)
86+
{
87+
_http.Dispose();
88+
}
89+
}
90+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<LangVersion>14</LangVersion>
9+
<IsPackable>false</IsPackable>
10+
<GenerateDocumentationFile>false</GenerateDocumentationFile>
11+
<NoWarn>$(NoWarn);1591</NoWarn>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\src\CoreIdent.Client\CoreIdent.Client.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)