Skip to content

Commit 9ce9dd7

Browse files
RolandGuijtrolandRoland GuijtkhalidabuhakmehErwinvandervalk
authored
roland/miscbff (#297)
* Update samples * Update tokenexchange sample * Add missing reference * Refactor OpenAPI response transformer and document writer Improve readability of `ProxyingOpenApiDocument` logic. Update OpenAPI document writing method to handle JSON serialization asynchronously and add inline comments for future compatibility issues with OpenAPI library and .NET 10. * make sample work again * cleanup * Update Docker sample --------- Co-authored-by: roland <roland.guijt@gmail.com> Co-authored-by: Roland Guijt <roland.guijt@duendesoftware.com> Co-authored-by: khalidabuhakmeh <khalidabuhakmeh@gmail.com> Co-authored-by: Erwin van der Valk <erwin@vandervalk.pro>
1 parent c49bce5 commit 9ce9dd7

144 files changed

Lines changed: 59007 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

BFF/v4/docker/.dockerignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
**/.dockerignore
2+
**/.env
3+
**/.git
4+
**/.gitignore
5+
**/.project
6+
**/.settings
7+
**/.toolstarget
8+
**/.vs
9+
**/.vscode
10+
**/.idea
11+
**/*.*proj.user
12+
**/*.dbmdl
13+
**/*.jfm
14+
**/azds.yaml
15+
**/bin
16+
**/charts
17+
**/docker-compose*
18+
**/Dockerfile*
19+
**/node_modules
20+
**/npm-debug.log
21+
**/obj
22+
**/secrets.dev.yaml
23+
**/values.dev.yaml
24+
LICENSE
25+
README.md
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Duende.IdentityServer;
2+
using Duende.IdentityServer.Models;
3+
4+
namespace ContainerizedIdentityServer;
5+
6+
public static class Config
7+
{
8+
public static IEnumerable<IdentityResource> IdentityResources =>
9+
new IdentityResource[]
10+
{
11+
new IdentityResources.OpenId(),
12+
new IdentityResources.Profile(),
13+
};
14+
15+
public static IEnumerable<ApiScope> ApiScopes =>
16+
new ApiScope[]
17+
{
18+
new ApiScope("scope2"), // Example scope for interactive client
19+
};
20+
21+
public static IEnumerable<Client> Clients =>
22+
new Client[]
23+
{
24+
new Client
25+
{
26+
ClientId = "interactive",
27+
ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
28+
29+
AllowedGrantTypes = GrantTypes.Code,
30+
31+
RedirectUris = { "https://localhost:5051/signin-oidc" },
32+
FrontChannelLogoutUri = "https://localhost:5051/signout-oidc",
33+
PostLogoutRedirectUris = { "https://localhost:5051/signout-callback-oidc" },
34+
35+
AllowOfflineAccess = true,
36+
AllowedScopes = {
37+
IdentityServerConstants.StandardScopes.OpenId,
38+
IdentityServerConstants.StandardScopes.Profile,
39+
IdentityServerConstants.StandardScopes.OfflineAccess,
40+
"scope2"
41+
42+
}
43+
},
44+
};
45+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Duende.IdentityServer" Version="7.4.5" />
12+
13+
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.3" />
14+
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<Content Include="..\.dockerignore">
19+
<Link>.dockerignore</Link>
20+
</Content>
21+
</ItemGroup>
22+
23+
24+
25+
</Project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
2+
WORKDIR /app
3+
4+
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
5+
ARG BUILD_CONFIGURATION=Release
6+
WORKDIR /src
7+
COPY ["ContainerizedIdentityServer/ContainerizedIdentityServer.csproj", "ContainerizedIdentityServer/"]
8+
RUN dotnet restore "ContainerizedIdentityServer/ContainerizedIdentityServer.csproj"
9+
COPY . .
10+
WORKDIR "/src/ContainerizedIdentityServer"
11+
RUN dotnet build "ContainerizedIdentityServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
12+
13+
FROM build AS publish
14+
ARG BUILD_CONFIGURATION=Release
15+
RUN dotnet publish "ContainerizedIdentityServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
16+
17+
FROM base AS final
18+
WORKDIR /app
19+
20+
COPY --from=publish /app/publish .
21+
22+
RUN mkdir -p /app/keys
23+
RUN chown 1654:1654 /app/keys
24+
25+
USER 1654
26+
27+
ENTRYPOINT ["dotnet", "ContainerizedIdentityServer.dll"]
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Duende.IdentityServer;
2+
using ContainerizedIdentityServer;
3+
using Microsoft.AspNetCore.Mvc.RazorPages;
4+
using Serilog;
5+
6+
namespace ContainerizedIdentityServer;
7+
8+
internal static class HostingExtensions
9+
{
10+
public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
11+
{
12+
builder.Services.AddRazorPages();
13+
14+
var isBuilder = builder.Services.AddIdentityServer(options =>
15+
{
16+
options.Events.RaiseErrorEvents = true;
17+
options.Events.RaiseInformationEvents = true;
18+
options.Events.RaiseFailureEvents = true;
19+
options.Events.RaiseSuccessEvents = true;
20+
21+
// see https://docs.duendesoftware.com/identityserver/fundamentals/resources#resources-isolation
22+
options.EmitStaticAudienceClaim = true;
23+
options.IssuerUri = "https://localhost:7051";
24+
})
25+
.AddTestUsers(TestUsers.Users);
26+
27+
// in-memory, code config
28+
isBuilder.AddInMemoryIdentityResources(Config.IdentityResources);
29+
isBuilder.AddInMemoryApiScopes(Config.ApiScopes);
30+
isBuilder.AddInMemoryClients(Config.Clients);
31+
32+
33+
// if you want to use server-side sessions: https://blog.duendesoftware.com/posts/20220406_session_management/
34+
// then enable it
35+
//isBuilder.AddServerSideSessions();
36+
//
37+
// and put some authorization on the admin/management pages
38+
//builder.Services.AddAuthorization(options =>
39+
// options.AddPolicy("admin",
40+
// policy => policy.RequireClaim("sub", "1"))
41+
// );
42+
//builder.Services.Configure<RazorPagesOptions>(options =>
43+
// options.Conventions.AuthorizeFolder("/ServerSideSessions", "admin"));
44+
45+
46+
builder.Services.AddAuthentication()
47+
.AddGoogle(options =>
48+
{
49+
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
50+
51+
// register your IdentityServer with Google at https://console.developers.google.com
52+
// enable the Google+ API
53+
// set the redirect URI to https://localhost:5001/signin-google
54+
options.ClientId = "copy client ID from Google here";
55+
options.ClientSecret = "copy client secret from Google here";
56+
});
57+
58+
return builder.Build();
59+
}
60+
61+
public static WebApplication ConfigurePipeline(this WebApplication app)
62+
{
63+
app.UseSerilogRequestLogging();
64+
65+
if (app.Environment.IsDevelopment())
66+
{
67+
app.UseDeveloperExceptionPage();
68+
}
69+
70+
app.UseStaticFiles();
71+
app.UseRouting();
72+
app.UseIdentityServer();
73+
app.UseAuthorization();
74+
75+
app.MapRazorPages()
76+
.RequireAuthorization();
77+
78+
return app;
79+
}
80+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@page
2+
@model ContainerizedIdentityServer.Pages.Account.AccessDeniedModel
3+
@{
4+
}
5+
<div class="row">
6+
<div class="col">
7+
<h1>Access Denied</h1>
8+
<p>You do not have permission to access that resource.</p>
9+
</div>
10+
</div>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Mvc.RazorPages;
5+
6+
namespace ContainerizedIdentityServer.Pages.Account;
7+
8+
public class AccessDeniedModel : PageModel
9+
{
10+
public void OnGet()
11+
{
12+
}
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@page
2+
@model ContainerizedIdentityServer.Pages.Create.Index
3+
4+
<div class="login-page">
5+
<div class="lead">
6+
<h1>Create Account</h1>
7+
</div>
8+
9+
<partial name="_ValidationSummary" />
10+
11+
<div class="row">
12+
13+
<div class="col-sm-6">
14+
<form asp-page="/Account/Create/Index">
15+
<input type="hidden" asp-for="Input.ReturnUrl" />
16+
17+
<div class="form-group">
18+
<label asp-for="Input.Username"></label>
19+
<input class="form-control" placeholder="Username" asp-for="Input.Username" autofocus>
20+
</div>
21+
<div class="form-group">
22+
<label asp-for="Input.Password"></label>
23+
<input type="password" class="form-control" placeholder="Password" asp-for="Input.Password" autocomplete="off">
24+
</div>
25+
<div class="form-group">
26+
<label asp-for="Input.Name"></label>
27+
<input type="text" class="form-control" placeholder="Name" asp-for="Input.Name">
28+
</div>
29+
<div class="form-group">
30+
<label asp-for="Input.Email"></label>
31+
<input type="email" class="form-control" placeholder="Email" asp-for="Input.Email" >
32+
</div>
33+
34+
<button class="btn btn-primary" name="Input.Button" value="create">Create</button>
35+
<button class="btn btn-secondary" name="Input.Button" value="cancel">Cancel</button>
36+
</form>
37+
</div>
38+
39+
</div>
40+
</div>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using Duende.IdentityServer;
5+
using Duende.IdentityServer.Models;
6+
using Duende.IdentityServer.Services;
7+
using Duende.IdentityServer.Test;
8+
using Microsoft.AspNetCore.Authentication;
9+
using Microsoft.AspNetCore.Authorization;
10+
using Microsoft.AspNetCore.Mvc;
11+
using Microsoft.AspNetCore.Mvc.RazorPages;
12+
13+
namespace ContainerizedIdentityServer.Pages.Create;
14+
15+
[SecurityHeaders]
16+
[AllowAnonymous]
17+
public class Index : PageModel
18+
{
19+
private readonly TestUserStore _users;
20+
private readonly IIdentityServerInteractionService _interaction;
21+
22+
[BindProperty]
23+
public InputModel Input { get; set; } = default!;
24+
25+
public Index(
26+
IIdentityServerInteractionService interaction,
27+
TestUserStore? users = null)
28+
{
29+
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
30+
_users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
31+
32+
_interaction = interaction;
33+
}
34+
35+
public IActionResult OnGet(string? returnUrl)
36+
{
37+
Input = new InputModel { ReturnUrl = returnUrl };
38+
return Page();
39+
}
40+
41+
public async Task<IActionResult> OnPost()
42+
{
43+
// check if we are in the context of an authorization request
44+
var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
45+
46+
// the user clicked the "cancel" button
47+
if (Input.Button != "create")
48+
{
49+
if (context != null)
50+
{
51+
// if the user cancels, send a result back into IdentityServer as if they
52+
// denied the consent (even if this client does not require consent).
53+
// this will send back an access denied OIDC error response to the client.
54+
await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
55+
56+
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
57+
if (context.IsNativeClient())
58+
{
59+
// The client is native, so this change in how to
60+
// return the response is for better UX for the end user.
61+
return this.LoadingPage(Input.ReturnUrl);
62+
}
63+
64+
return Redirect(Input.ReturnUrl ?? "~/");
65+
}
66+
else
67+
{
68+
// since we don't have a valid context, then we just go back to the home page
69+
return Redirect("~/");
70+
}
71+
}
72+
73+
if (_users.FindByUsername(Input.Username) != null)
74+
{
75+
ModelState.AddModelError("Input.Username", "Invalid username");
76+
}
77+
78+
if (ModelState.IsValid)
79+
{
80+
var user = _users.CreateUser(Input.Username, Input.Password, Input.Name, Input.Email);
81+
82+
// issue authentication cookie with subject ID and username
83+
var isuser = new IdentityServerUser(user.SubjectId)
84+
{
85+
DisplayName = user.Username
86+
};
87+
88+
await HttpContext.SignInAsync(isuser);
89+
90+
if (context != null)
91+
{
92+
if (context.IsNativeClient())
93+
{
94+
// The client is native, so this change in how to
95+
// return the response is for better UX for the end user.
96+
return this.LoadingPage(Input.ReturnUrl);
97+
}
98+
99+
// we can trust Input.ReturnUrl since GetAuthorizationContextAsync returned non-null
100+
return Redirect(Input.ReturnUrl ?? "~/");
101+
}
102+
103+
// request for a local page
104+
if (Url.IsLocalUrl(Input.ReturnUrl))
105+
{
106+
return Redirect(Input.ReturnUrl);
107+
}
108+
else if (string.IsNullOrEmpty(Input.ReturnUrl))
109+
{
110+
return Redirect("~/");
111+
}
112+
else
113+
{
114+
// user might have clicked on a malicious link - should be logged
115+
throw new ArgumentException("invalid return URL");
116+
}
117+
}
118+
119+
return Page();
120+
}
121+
}

0 commit comments

Comments
 (0)