From 1d87f652c7c0e465accc8e7af5004f6b64d3e2ba Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sun, 12 Feb 2023 19:19:46 +0700 Subject: [PATCH] (#181) Adding OpenIddict --- .../Certs/CreateSelfSignedCertificate.ps1 | 2 +- .../Certs/classifiedads.identityserver.pfx | Bin 2741 -> 2741 bytes .../IdentityServerOptions.cs | 4 +- .../Controllers/AuthorizationController.cs | 107 ++++++- .../Extensions/PrincipalExtensions.cs | 4 +- .../HostedServices/SeedDataHostedService.cs | 289 ++++++++++++++++-- .../ClassifiedAds.IdentityServer/Startup.cs | 5 +- .../appsettings.json | 7 +- .../Web/Authentication/TokenManager.cs | 55 ---- src/Microservices/docker-compose.yml | 2 +- .../Certs/classifiedads.identityserver.pfx | Bin 0 -> 2741 bytes .../ClassifiedAds.WebAPI.csproj | 6 + .../IdentityServerAuthentication.cs | 17 +- .../ClassifiedAds.WebAPI/Startup.cs | 20 +- .../ClassifiedAds.WebAPI/appsettings.json | 16 +- src/ModularMonolith/docker-compose.yml | 2 +- .../Certs/classifiedads.identityserver.pfx | Bin 0 -> 2741 bytes .../ClassifiedAds.WebAPI.csproj | 3 + .../IdentityServerAuthentication.cs | 17 +- src/Monolith/ClassifiedAds.WebAPI/Startup.cs | 20 +- .../ClassifiedAds.WebAPI/appsettings.json | 16 +- src/Monolith/docker-compose.yml | 2 +- 22 files changed, 485 insertions(+), 109 deletions(-) delete mode 100644 src/IdentityServer/OpenIddict/ClassifiedAds.Infrastructure/Web/Authentication/TokenManager.cs create mode 100644 src/ModularMonolith/ClassifiedAds.WebAPI/Certs/classifiedads.identityserver.pfx create mode 100644 src/Monolith/ClassifiedAds.WebAPI/Certs/classifiedads.identityserver.pfx diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Certs/CreateSelfSignedCertificate.ps1 b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Certs/CreateSelfSignedCertificate.ps1 index 3a011cadb..6d8733eec 100644 --- a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Certs/CreateSelfSignedCertificate.ps1 +++ b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Certs/CreateSelfSignedCertificate.ps1 @@ -1,5 +1,5 @@ $date_now = Get-Date -$extended_date = $date_now.AddYears(3) +$extended_date = $date_now.AddYears(10) $cert = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname classifiedads.identityserver -notafter $extended_date $pwd = ConvertTo-SecureString -String 'password1234' -Force -AsPlainText $path = 'cert:\localMachine\my\' + $cert.thumbprint diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Certs/classifiedads.identityserver.pfx b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Certs/classifiedads.identityserver.pfx index af8891bfaa621d67f2d41d623cbb3c22de2660ec..ae027da8336711a4c74f1b11dbb093d249dbba82 100644 GIT binary patch delta 2468 zcmV;V30wBH6}1(RXn)4CXF>V=ifsY{2haq91lam;Fgxf1Gs;?qMYj^w^1&~y(dop+2X=b)Vl_EWa3)1TgylmI45h}${b|5)W!uK zjq^9s9~a>aA<{+0QWrM$1}YF9nGw$sGs@A!h<^~HL&TkPy+l9CCAe!# z3UaZ4E63*DKZ@}zp4=w!makb=OlNlJ3mBQ-A3oN$!fnvSip~nDr%5xj2P^LhhHdq6 zbrELiNYVuUWxuTKI}VBe*9{5Hy0OKK2pqoru424u?Y*rc^hncc>>iB_8%4B;JsCb# z#chX=Q`K?!fq&?-Q8ae`C3A21kHRia6WO6Z7tbXI=(b~B2qdB_B)vgT0{oPOwA@aP zn)U;F(wIPIgzKG5IYyoRJnjaa(cy<6iT|NzL=e1Kwp{}t;c@0*5 zQb(QR7B%fq&;H%QGPRG1&~_4MA=Pa#hjpEt5S?{pmffvKN?ZYgBz2Kq?;HGvg8jWU~8CCEu`3pQ#%R7kVe;DI?b3d)Ntd6jrv zCqmJQoP>dhT`Nb1pLg8~3RP~JO#Xz-lD<+P@ zMxk&w&rS#;^f`7_fid8O(A5|JjquBaC+Ko_uzx;QMNWpGLsZ)_C2!VU~35E zh_Tiq@dH^oNg00bViB+OIa~0iP|Jui0p`?g%eQk&$c^GkwbZ&;NOl5JZr|0af*ZgF zUz(NLWCw-l$&_=QlA5G?C2oygd|F7SsFg8{L8Y|DdOV>Ra@(^SkWz2n=W>s#n!pp8 z!GGX0&C#(c5Z}$i8N4v?TwFqLEnVuZNni>>0A zBl#VvC-D%{vz0l_?euoNSJ)vNirL#H{=8o4?C4<~&DSD3X&&pYvpxEU05FdO5(rR} z+^M2SN%5I7V>AF1g=Qok0g(C4Z>Ur>2pR>-bU)E&Y0nWY}v1+H{0YQqTNF z%I_w(K|#0~!8>{OO@=k1@L&f4@%LZ9n!^%vJLNG*g%6_92Y_(2)@%3vz-jYUdd)^< z&Zq+X?qsXvin=(4e&|6MpTA7CUNlvWBRolgI9PnUQPAIfx~v}iBi3h-J(?UWe1AWf zsl0@^+rzzrxVJOso`kFkGCU{9^Ijt%@QEQx2EzlEuSbDoQBoN=vPgsI{ahVPp1O~e zpoa=R^B&S?{Q?R;uYwtRz8N@we$tnWTE?+gl=$umo-nx6A{V*OJP95nmtZmHCox)H0U4LuY?)D;go#~p49NXAZlInKGM#fU%f-L^wv?V8^ zhyc6D^zQwUgBP2K^-KtR$JVRF;!&z3xb)|$xcA9i>VO-+hZ$nO)2AEXzk4c9Uc=7C z@BB0vSSMn~HP;)Dl@W&s2+`p1gXCTKqt($0R#bpHrUlWRgPcRL8qmj*7RQT2J9d1N$hTPH)CGEc&mZka_WN8 z1&?U$SpRLI@?s^(=jFfyi>l81cu!E9+ide7{pIBr>&9%B;v5q~%SWDc|$^|8TLLm3n|-K zY;v-(JuQjFd9F#QnC6W9)Q(o}BJenHR#3*q#4*luS@7qrXZGp+;=HimW}T2Eft9#N zWY!K|kn-nHH!?V~hOw^BGfhhUSA*(dabOWMpNPo8JWn3j%;`JT# zsx@Q`aKJlLGtn7; zf?!q&AyS$y#k&dEhy7HWe4n>16YCD7U_~2~A#6>|Un5huS;LWn-?d?N3%%lu(k{QH5Bmgv;t+0v$y9ls&?#yJe0jkR!Bi$5ylfn zZ5ZZu=Wk>>sej0&Kyf56p&T`UNT7xOAoMD^D#RE;eXLm)_rJ*Khd zU>}s>*Q+J>lQ(3vWBCx3?|tgP+*rt0FQk(J#*(>rPEW22xxf@s2qU|SKIGe~lcO`p zsqu2ymsexWDzO5cm)`G>}!1!wgwhm3kMO?L9SeEP>G5m;7J7JPrOjI`xj(C6F-(c#c zr8g;TWKCf8j_22dV+To>5dq&te*Q2!Fdr}n1_dh)0|FWZ6qkm}wtD_{f67TDR=@=0 iCp0lJ_5>7Vlih;fNC&=fa)0NnD~npGgo4=u0te8@udUYr delta 2468 zcmV;V30wBH6}1(RXn*v5ULl-~?uP;b2haq91lav7#zOGrI5!XX^Gf8r4LhQ|q_l8| zrJi8?ObZ0~m+0*UBN*v3bO}HgaJES)WjQ`ACgutNo#yPGVy-xQO|g@D04C+mLt0N~ z;&(@m#bH~VJs^Xg+K}x|wG2n#2E<3&<(MxKYQ^-@x=wj7Xn%1mu`Da`ywS4tGH;&T z35HijEB~C-IS&^Q@&~GChH<#bJXX&@36(LU@PHoNhc*?acwFW3)Hv?{_5Cxa80%8-SA<&~$cb z9yH!N`^Gcg{(n;06k1NqZi=RMyQXrv#Ec|JB};!hL)NueVu>p-p_{nckqY)f}oV< zYgc2#2m)z|<>#xmH=-3|%Ol((ps zXGmK%pam~fEKx9_pLJnV&p=*+d?9%RxJpA51hEJv+##(#2k)YpB_I4fKe!086f-hU&!9Dc{i z6)${0QCE=2Y>H?HqaUtfyWZrjq|>uDVil8r=fyiB#F@GgK`tq^KzM*w?pCUY_{=i& zWNgsuUc`)x_{A^$(TzhCaZL}he^5bVNr;9y9jwoy z_J0(d>|uU5CcI;#b<~3}Mqp{qY>&fazS2{5ZT#hQEn?}0@<>uM14f*5TE_STn-!Al z!U1mzVGd7r^OsDJv!#xjV*75Ch`6(tiwYw|``2TZ`@QXdr>C8r=A{Tnx_`L?U!zOf zLj~mNYv<<}l?Cc3Ke%wj8;qSB$sj7ggnwC5+*)jl270k2chv$hKy#vOrjo_@ADp#^ zJ%##uM(!2~w=H3$h)8?3l9>n>5jEp3 zj>T%cV)G6($0R#bpHGcP0!{`DU#NEn&0TFV7yVtYnBxy6rl4) z5fZWK5%$<3JgZ4CjuaB=PzhI&NCm^7>7)8RiQj)VLnm{?hO)KvO#%tG7=OFO8Cq?X z&3ncA2Cq)(sE}&xErnIc6p@>4OrG8RP!SRL2yE%*l2eHQ5XTc=YWjUguizm}ziVZ1 z!C{x0r4i|tz~eT3@JXZWn|(OYU5P$5aoE>7PGx;DYoHIqXM)^Hb^2QjjQe2^bF`N~uiqTfY z*;9GbC_E=7h{1P$wt}1iytjVATIKzu8Z#6Bt_{+#pyIvoadQ;fP?y=ghtZnWIP|^J zGn1}@R)QxSY~rUM3G0a2^dnKvsU}tQ#t8fP<3-{n41W>Drjaj)4*hQ$ z@{>6tT-KIcB1EXaPpie!#?ff+K;nn+*3O-|1-#9jOU+>f`P>3=_01hOyIH7WuEpQTWwf7D6!@ASy!(n=5YLO!*luZT9+ke$)_7E5 z(}DGHB-79MB^bs43>03x?&N9IX)0I#a{nG%QiKp^w2`chI!QrwAU)PEwM>W0t;MF2E`O4*!}Rcc5E}f8gk}XsYJSsm_8PPbC^8J1+*8Uyj@t!h2rP zoR2X|LvYp$D*Mw-;1nH4GZ#-dK|zuv{l z3Qr2o@=bH%#V#78DeGb$)U#l{{gE&`Fdr}n1_dh)0|FWZ6lzjnzW!E-m*f9yGXxn_ ic-Cx}^aK>JKDpy;_dgcjM{m6r2=Gb%H; _userManager; private readonly SignInManager _signInManager; - public AuthorizationController(SignInManager signInManager) + public AuthorizationController( + UserManager userManager, + SignInManager signInManager) { + _userManager = userManager; _signInManager = signInManager; } @@ -69,8 +74,7 @@ public async Task Authorize() [HttpPost("~/connect/token")] public async Task Exchange() { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); ClaimsPrincipal claimsPrincipal; @@ -101,6 +105,57 @@ public async Task Exchange() // Retrieve the claims principal stored in the refresh token. claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; } + else if (request.IsPasswordGrantType()) + { + var user = await _userManager.FindByNameAsync(request.Username); + if (user == null) + { + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." + }); + + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + + // Validate the username/password parameters and ensure the account is not locked out. + var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true); + if (!result.Succeeded) + { + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." + }); + + return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + + // Create the claims-based identity that will be used by OpenIddict to generate tokens. + var identity = new ClaimsIdentity( + authenticationType: TokenValidationParameters.DefaultAuthenticationType, + nameType: OpenIddictConstants.Claims.Name, + roleType: OpenIddictConstants.Claims.Role); + + // Add the claims that will be persisted in the tokens. + identity.SetClaim(OpenIddictConstants.Claims.Subject, await _userManager.GetUserIdAsync(user)) + .SetClaim(OpenIddictConstants.Claims.Email, await _userManager.GetEmailAsync(user)) + .SetClaim(OpenIddictConstants.Claims.Name, await _userManager.GetUserNameAsync(user)); + + // Set the list of scopes granted to the client application. + identity.SetScopes(new[] + { + OpenIddictConstants.Scopes.OpenId, + OpenIddictConstants.Scopes.Email, + OpenIddictConstants.Scopes.Profile, + OpenIddictConstants.Scopes.Roles + }.Intersect(request.GetScopes())); + + identity.SetDestinations(GetDestinations); + + return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } else { throw new InvalidOperationException("The specified grant type is not supported."); @@ -134,5 +189,51 @@ public async Task LogoutPost() }); } + private static IEnumerable GetDestinations(Claim claim) + { + // Note: by default, claims are NOT automatically included in the access and identity tokens. + // To allow OpenIddict to serialize them, you must attach them a destination, that specifies + // whether they should be included in access tokens, in identity tokens or in both. + + switch (claim.Type) + { + case OpenIddictConstants.Claims.Name: + yield return OpenIddictConstants.Destinations.AccessToken; + + if (claim.Subject.HasScope(OpenIddictConstants.Scopes.Profile)) + { + yield return OpenIddictConstants.Destinations.IdentityToken; + } + + yield break; + + case OpenIddictConstants.Claims.Email: + yield return OpenIddictConstants.Destinations.AccessToken; + + if (claim.Subject.HasScope(OpenIddictConstants.Scopes.Email)) + { + yield return OpenIddictConstants.Destinations.IdentityToken; + } + + yield break; + + case OpenIddictConstants.Claims.Role: + yield return OpenIddictConstants.Destinations.AccessToken; + + if (claim.Subject.HasScope(OpenIddictConstants.Scopes.Roles)) + { + yield return OpenIddictConstants.Destinations.IdentityToken; + } + + yield break; + + // Never include the security stamp in the access and identity tokens, as it's a secret value. + case "AspNet.Identity.SecurityStamp": yield break; + + default: + yield return OpenIddictConstants.Destinations.AccessToken; + yield break; + } + } } } diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Extensions/PrincipalExtensions.cs b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Extensions/PrincipalExtensions.cs index 35d0f94c6..d918edf7b 100644 --- a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Extensions/PrincipalExtensions.cs +++ b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Extensions/PrincipalExtensions.cs @@ -1,4 +1,4 @@ -using IdentityModel; +using OpenIddict.Abstractions; using System.Security.Claims; namespace ClassifiedAds.IdentityServer.Extensions @@ -13,7 +13,7 @@ public static string GetDisplayName(this ClaimsPrincipal principal) return name; } - var sub = principal.FindFirst(JwtClaimTypes.Subject); + var sub = principal.FindFirst(OpenIddictConstants.Claims.Subject); if (sub != null) { return sub.Value; diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/HostedServices/SeedDataHostedService.cs b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/HostedServices/SeedDataHostedService.cs index 341bb9dae..abeab2ade 100644 --- a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/HostedServices/SeedDataHostedService.cs +++ b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/HostedServices/SeedDataHostedService.cs @@ -22,39 +22,266 @@ public async Task StartAsync(CancellationToken cancellationToken) var manager = scope.ServiceProvider.GetRequiredService(); - if (await manager.FindByClientIdAsync("ClassifiedAds.WebMVC", cancellationToken) is null) - { - await manager.CreateAsync(new OpenIddictApplicationDescriptor - { - ClientId = "ClassifiedAds.WebMVC", - ClientSecret = "secret", - DisplayName = "ClassifiedAds Web MVC", - RedirectUris = { new Uri("https://localhost:44364/signin-oidc") }, - PostLogoutRedirectUris = { new Uri("https://localhost:44364/signout-callback-oidc") }, - Permissions = - { - OpenIddictConstants.Permissions.Endpoints.Authorization, - OpenIddictConstants.Permissions.Endpoints.Token, - OpenIddictConstants.Permissions.Endpoints.Logout, - - OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, - OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, - OpenIddictConstants.Permissions.GrantTypes.RefreshToken, - - OpenIddictConstants.Permissions.Prefixes.Scope + "openid", - OpenIddictConstants.Permissions.Scopes.Profile, - OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", - OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", - OpenIddictConstants.Permissions.ResponseTypes.Code - }, - Requirements = - { - OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange - } - }, cancellationToken); - } + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "Swagger", + ClientSecret = "secret", + DisplayName = "Swagger", + RedirectUris = + { + new Uri("https://localhost:44312/oauth2-redirect.html") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Confidential, + }, cancellationToken); + + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "ClassifiedAds.WebMVC", + ClientSecret = "secret", + DisplayName = "ClassifiedAds Web MVC", + RedirectUris = + { + new Uri("https://localhost:44364/signin-oidc") + }, + PostLogoutRedirectUris = + { + new Uri("https://localhost:44364/signout-callback-oidc") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.Password, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Confidential, + }, cancellationToken); + + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "ClassifiedAds.BlazorServerSide", + ClientSecret = "secret", + DisplayName = "ClassifiedAds BlazorServerSide", + RedirectUris = + { + new Uri("https://localhost:44331/signin-oidc") + }, + PostLogoutRedirectUris = + { + new Uri("https://localhost:44331/signout-callback-oidc") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Confidential, + }, cancellationToken); + + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "ClassifiedAds.BlazorWebAssembly", + DisplayName = "ClassifiedAds BlazorWebAssembly", + RedirectUris = + { + new Uri("https://localhost:44348/authentication/login-callback") + }, + PostLogoutRedirectUris = + { + new Uri("https://localhost:44348/authentication/logout-callback") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Public, + }, cancellationToken); + + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "ClassifiedAds.Angular", + DisplayName = "ClassifiedAds Angular", + RedirectUris = + { + new Uri("http://localhost:4200/oidc-login-redirect") + }, + PostLogoutRedirectUris = + { + new Uri("http://localhost:4200/?postLogout=true") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Public, + }, cancellationToken); + + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "ClassifiedAds.React", + DisplayName = "ClassifiedAds React", + RedirectUris = + { + new Uri("http://localhost:3000/oidc-login-redirect") + }, + PostLogoutRedirectUris = + { + new Uri("http://localhost:3000/?postLogout=true") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Public, + }, cancellationToken); + + await UpsertClientApplication(manager, new OpenIddictApplicationDescriptor + { + ClientId = "ClassifiedAds.Vue", + DisplayName = "ClassifiedAds Vue", + RedirectUris = + { + new Uri("http://localhost:8080/oidc-login-redirect") + }, + PostLogoutRedirectUris = + { + new Uri("http://localhost:8080/?postLogout=true") + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + + OpenIddictConstants.Permissions.Prefixes.Scope + "openid", + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + "ClassifiedAds.WebAPI", + OpenIddictConstants.Permissions.Prefixes.Scope + "offline_access", + OpenIddictConstants.Permissions.ResponseTypes.Code + }, + Requirements = + { + OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange + }, + Type = OpenIddictConstants.ClientTypes.Public, + }, cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + private static async Task UpsertClientApplication(IOpenIddictApplicationManager manager, OpenIddictApplicationDescriptor openIddictApplicationDescriptor, CancellationToken cancellationToken) + { + var client = await manager.FindByClientIdAsync(openIddictApplicationDescriptor.ClientId, cancellationToken); + + if (client is null) + { + await manager.CreateAsync(openIddictApplicationDescriptor, cancellationToken); + } + else + { + await manager.UpdateAsync(client, openIddictApplicationDescriptor, cancellationToken); + } + } } } diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Startup.cs b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Startup.cs index df4d55b47..89edcf4bd 100644 --- a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Startup.cs +++ b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/Startup.cs @@ -103,12 +103,13 @@ public void ConfigureServices(IServiceCollection services) options.AllowAuthorizationCodeFlow() .AllowHybridFlow() .AllowClientCredentialsFlow() + .AllowPasswordFlow() .AllowRefreshTokenFlow(); options.RegisterScopes(Scopes.OpenId, Scopes.Profile, Scopes.OfflineAccess, "ClassifiedAds.WebAPI"); - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); + options.AddEncryptionCertificate(AppSettings.IdentityServer.EncryptionCertificate.FindCertificate()) + .AddSigningCertificate(AppSettings.IdentityServer.SigningCertificate.FindCertificate()); options .UseAspNetCore() diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/appsettings.json b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/appsettings.json index 10263c40a..c38e1c487 100644 --- a/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/appsettings.json +++ b/src/IdentityServer/OpenIddict/ClassifiedAds.IdentityServer/appsettings.json @@ -4,7 +4,12 @@ }, "IdentityServer": { "IssuerUri": "", - "Certificate": { + "EncryptionCertificate": { + "Thumbprint": null, + "Path": "Certs/classifiedads.identityserver.pfx", + "Password": "password1234" + }, + "SigningCertificate": { "Thumbprint": null, "Path": "Certs/classifiedads.identityserver.pfx", "Password": "password1234" diff --git a/src/IdentityServer/OpenIddict/ClassifiedAds.Infrastructure/Web/Authentication/TokenManager.cs b/src/IdentityServer/OpenIddict/ClassifiedAds.Infrastructure/Web/Authentication/TokenManager.cs deleted file mode 100644 index a186e6af9..000000000 --- a/src/IdentityServer/OpenIddict/ClassifiedAds.Infrastructure/Web/Authentication/TokenManager.cs +++ /dev/null @@ -1,55 +0,0 @@ -using IdentityModel.Client; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using System; -using System.Net.Http; -using System.Threading.Tasks; - -namespace ClassifiedAds.Infrastructure.Web.Authentication -{ - public class TokenManager - { - private readonly IHttpClientFactory _httpClientFactory; - private readonly OpenIdConnectOptions _options; - - public TokenManager(IHttpClientFactory httpClientFactory, OpenIdConnectOptions options) - { - _httpClientFactory = httpClientFactory; - _options = options; - } - - public async Task RefreshToken(string refreshToken) - { - var httpClient = _httpClientFactory.CreateClient(); - var metaDataResponse = await httpClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest - { - Address = _options.Authority, - Policy = { RequireHttps = _options.RequireHttpsMetadata }, - }); - - var response = await httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest - { - Address = metaDataResponse.TokenEndpoint, - ClientId = _options.ClientId, - ClientSecret = _options.ClientSecret, - RefreshToken = refreshToken, - }); - - if (response.IsError) - { - if (response.HttpStatusCode == System.Net.HttpStatusCode.BadRequest) - { - return null; - } - - throw new Exception(response.Raw); - } - - return new TokenModel - { - AccessToken = response.AccessToken, - RefreshToken = response.RefreshToken, - ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn), - }; - } - } -} diff --git a/src/Microservices/docker-compose.yml b/src/Microservices/docker-compose.yml index 3ff7b66e4..f71124ee4 100644 --- a/src/Microservices/docker-compose.yml +++ b/src/Microservices/docker-compose.yml @@ -83,7 +83,7 @@ services: image: classifiedads.services.identity.authserver build: context: ../IdentityServer/IdentityServer4 - dockerfile: ../IdentityServer/IdentityServer4/ClassifiedAds.IdentityServer/Dockerfile + dockerfile: ./ClassifiedAds.IdentityServer/Dockerfile ports: - "9000:80" depends_on: diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/Certs/classifiedads.identityserver.pfx b/src/ModularMonolith/ClassifiedAds.WebAPI/Certs/classifiedads.identityserver.pfx new file mode 100644 index 0000000000000000000000000000000000000000..ae027da8336711a4c74f1b11dbb093d249dbba82 GIT binary patch literal 2741 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 literal 0 HcmV?d00001 diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj b/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj index 1dcc14666..283b2a4d9 100644 --- a/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj @@ -40,4 +40,10 @@ + + + Always + + + diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs b/src/ModularMonolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs index f32ca54d3..20477864f 100644 --- a/src/ModularMonolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs @@ -1,11 +1,26 @@ -namespace ClassifiedAds.WebAPI.ConfigurationOptions +using CryptographyHelper.Certificates; + +namespace ClassifiedAds.WebAPI.ConfigurationOptions { public class IdentityServerAuthentication { + public string Provider { get; set; } + public string Authority { get; set; } public string ApiName { get; set; } public bool RequireHttpsMetadata { get; set; } + + public OpenIddictOptions OpenIddict { get; set; } + } + + public class OpenIddictOptions + { + public string IssuerUri { get; set; } + + public CertificateOption TokenDecryptionCertificate { get; set; } + + public CertificateOption IssuerSigningCertificate { get; set; } } } diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs b/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs index e8477c1b5..374806642 100644 --- a/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerUI; using System; @@ -101,9 +102,22 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { - options.Authority = AppSettings.IdentityServerAuthentication.Authority; - options.Audience = AppSettings.IdentityServerAuthentication.ApiName; - options.RequireHttpsMetadata = AppSettings.IdentityServerAuthentication.RequireHttpsMetadata; + if (AppSettings.IdentityServerAuthentication.Provider == "OpenIddict") + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidIssuer = AppSettings.IdentityServerAuthentication.OpenIddict.IssuerUri, + TokenDecryptionKey = new X509SecurityKey(AppSettings.IdentityServerAuthentication.OpenIddict.TokenDecryptionCertificate.FindCertificate()), + IssuerSigningKey = new X509SecurityKey(AppSettings.IdentityServerAuthentication.OpenIddict.IssuerSigningCertificate.FindCertificate()), + }; + } + else + { + options.Authority = AppSettings.IdentityServerAuthentication.Authority; + options.Audience = AppSettings.IdentityServerAuthentication.ApiName; + options.RequireHttpsMetadata = AppSettings.IdentityServerAuthentication.RequireHttpsMetadata; + } }); services.AddSwaggerGen(setupAction => diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/appsettings.json b/src/ModularMonolith/ClassifiedAds.WebAPI/appsettings.json index c5705dad4..42f738de6 100644 --- a/src/ModularMonolith/ClassifiedAds.WebAPI/appsettings.json +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/appsettings.json @@ -1,8 +1,22 @@ { "IdentityServerAuthentication": { + "Provider": "IdentityServer4", "Authority": "https://localhost:44367", "ApiName": "ClassifiedAds.WebAPI", - "RequireHttpsMetadata": "true" + "RequireHttpsMetadata": "true", + "OpenIddict": { + "IssuerUri": "https://localhost:44367/", + "TokenDecryptionCertificate": { + "Thumbprint": null, + "Path": "Certs/classifiedads.identityserver.pfx", + "Password": "password1234" + }, + "IssuerSigningCertificate": { + "Thumbprint": null, + "Path": "Certs/classifiedads.identityserver.pfx", + "Password": "password1234" + } + } }, "Logging": { "LogLevel": { diff --git a/src/ModularMonolith/docker-compose.yml b/src/ModularMonolith/docker-compose.yml index 8bcdf15ca..d2a1998cf 100644 --- a/src/ModularMonolith/docker-compose.yml +++ b/src/ModularMonolith/docker-compose.yml @@ -39,7 +39,7 @@ services: image: classifiedads.modularmonolith.identityserver build: context: ../IdentityServer/IdentityServer4 - dockerfile: ../IdentityServer/IdentityServer4/ClassifiedAds.IdentityServer/Dockerfile + dockerfile: ./ClassifiedAds.IdentityServer/Dockerfile ports: - "9000:80" depends_on: diff --git a/src/Monolith/ClassifiedAds.WebAPI/Certs/classifiedads.identityserver.pfx b/src/Monolith/ClassifiedAds.WebAPI/Certs/classifiedads.identityserver.pfx new file mode 100644 index 0000000000000000000000000000000000000000..ae027da8336711a4c74f1b11dbb093d249dbba82 GIT binary patch literal 2741 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 literal 0 HcmV?d00001 diff --git a/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj b/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj index b3c1293d3..9d17d44be 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj +++ b/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj @@ -37,6 +37,9 @@ + + Always + Always diff --git a/src/Monolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs b/src/Monolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs index f32ca54d3..20477864f 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs +++ b/src/Monolith/ClassifiedAds.WebAPI/ConfigurationOptions/IdentityServerAuthentication.cs @@ -1,11 +1,26 @@ -namespace ClassifiedAds.WebAPI.ConfigurationOptions +using CryptographyHelper.Certificates; + +namespace ClassifiedAds.WebAPI.ConfigurationOptions { public class IdentityServerAuthentication { + public string Provider { get; set; } + public string Authority { get; set; } public string ApiName { get; set; } public bool RequireHttpsMetadata { get; set; } + + public OpenIddictOptions OpenIddict { get; set; } + } + + public class OpenIddictOptions + { + public string IssuerUri { get; set; } + + public CertificateOption TokenDecryptionCertificate { get; set; } + + public CertificateOption IssuerSigningCertificate { get; set; } } } diff --git a/src/Monolith/ClassifiedAds.WebAPI/Startup.cs b/src/Monolith/ClassifiedAds.WebAPI/Startup.cs index 678ead368..473270fc7 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/Startup.cs +++ b/src/Monolith/ClassifiedAds.WebAPI/Startup.cs @@ -22,6 +22,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerUI; using System; @@ -106,9 +107,22 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { - options.Authority = AppSettings.IdentityServerAuthentication.Authority; - options.Audience = AppSettings.IdentityServerAuthentication.ApiName; - options.RequireHttpsMetadata = AppSettings.IdentityServerAuthentication.RequireHttpsMetadata; + if (AppSettings.IdentityServerAuthentication.Provider == "OpenIddict") + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidIssuer = AppSettings.IdentityServerAuthentication.OpenIddict.IssuerUri, + TokenDecryptionKey = new X509SecurityKey(AppSettings.IdentityServerAuthentication.OpenIddict.TokenDecryptionCertificate.FindCertificate()), + IssuerSigningKey = new X509SecurityKey(AppSettings.IdentityServerAuthentication.OpenIddict.IssuerSigningCertificate.FindCertificate()), + }; + } + else + { + options.Authority = AppSettings.IdentityServerAuthentication.Authority; + options.Audience = AppSettings.IdentityServerAuthentication.ApiName; + options.RequireHttpsMetadata = AppSettings.IdentityServerAuthentication.RequireHttpsMetadata; + } }); services.AddAuthorizationPolicies(Assembly.GetExecutingAssembly(), AuthorizationPolicyNames.GetPolicyNames()); diff --git a/src/Monolith/ClassifiedAds.WebAPI/appsettings.json b/src/Monolith/ClassifiedAds.WebAPI/appsettings.json index 398220d64..9a226099a 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/appsettings.json +++ b/src/Monolith/ClassifiedAds.WebAPI/appsettings.json @@ -3,9 +3,23 @@ "ClassifiedAds": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#;MultipleActiveResultSets=true;Encrypt=False" }, "IdentityServerAuthentication": { + "Provider": "IdentityServer4", "Authority": "https://localhost:44367", "ApiName": "ClassifiedAds.WebAPI", - "RequireHttpsMetadata": "true" + "RequireHttpsMetadata": "true", + "OpenIddict": { + "IssuerUri": "https://localhost:44367/", + "TokenDecryptionCertificate": { + "Thumbprint": null, + "Path": "Certs/classifiedads.identityserver.pfx", + "Password": "password1234" + }, + "IssuerSigningCertificate": { + "Thumbprint": null, + "Path": "Certs/classifiedads.identityserver.pfx", + "Password": "password1234" + } + } }, "Logging": { "LogLevel": { diff --git a/src/Monolith/docker-compose.yml b/src/Monolith/docker-compose.yml index 86b9db39a..9a8870475 100644 --- a/src/Monolith/docker-compose.yml +++ b/src/Monolith/docker-compose.yml @@ -52,7 +52,7 @@ services: image: classifiedads.identityserver build: context: ../IdentityServer/IdentityServer4 - dockerfile: ../IdentityServer/IdentityServer4/ClassifiedAds.IdentityServer/Dockerfile + dockerfile: ./ClassifiedAds.IdentityServer/Dockerfile ports: - "9000:80" depends_on: