Skip to content

Commit 56136a5

Browse files
authored
Merge pull request #3 from DuendeSoftware/brock/update-is-7.0
update to .net8 and is7
2 parents 1649f98 + 2ab37df commit 56136a5

Some content is hidden

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

56 files changed

+1116
-428
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ on:
1616
env:
1717
AZURE_WEBAPP_NAME: DuendeSoftware-Demo # set this to your application's name
1818
AZURE_WEBAPP_PACKAGE_PATH: './publish' # set this to the path to your web app project, defaults to the repository root
19+
DOTNET_VERSION: '8.0.x' # set this to the dot net version to use
1920

2021
jobs:
2122
build-and-deploy:
@@ -28,6 +29,8 @@ jobs:
2829
# Setup .NET Core SDK
2930
- name: Setup .NET Core
3031
uses: actions/setup-dotnet@v1
32+
with:
33+
dotnet-version: ${{ env.DOTNET_VERSION }}
3134

3235
# Run dotnet build and publish
3336
- name: dotnet build and publish

src/DPoP/DPoPJwtBearerEvents.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using ApiHost;
21
using IdentityModel;
32
using Microsoft.AspNetCore.Authentication.JwtBearer;
3+
using Microsoft.AspNetCore.Http;
44
using Microsoft.Extensions.Options;
55
using Microsoft.Net.Http.Headers;
66
using System.Text;
@@ -131,7 +131,7 @@ public override Task Challenge(JwtBearerChallengeContext context)
131131
}
132132
}
133133

134-
context.Response.Headers.Add(HeaderNames.WWWAuthenticate, sb.ToString());
134+
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, sb.ToString());
135135

136136

137137
if (context.HttpContext.Items.ContainsKey("DPoP-Nonce"))

src/DPoP/DPoPProofValidatonContext.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Collections.Generic;
2-
using System.Security.Claims;
31

42
namespace DPoPApi;
53

src/DPoP/DPoPProofValidator.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using DPoPApi;
21
using IdentityModel;
32
using Microsoft.AspNetCore.DataProtection;
43
using Microsoft.Extensions.Logging;
@@ -13,7 +12,7 @@
1312
using System.Text.Json;
1413
using System.Threading.Tasks;
1514

16-
namespace ApiHost;
15+
namespace DPoPApi;
1716

1817
public class DPoPProofValidator
1918
{
@@ -83,12 +82,12 @@ public async Task<DPoPProofValidatonResult> ValidateAsync(DPoPProofValidatonCont
8382
return result;
8483
}
8584

86-
Logger.LogDebug("Successfully validated DPoP proof token");
85+
Logger.LogDebug("Successfully validated DPoP proof token with thumbprint: {jkt}", result.JsonWebKeyThumbprint);
8786
result.IsError = false;
8887
}
8988
finally
9089
{
91-
if (result.IsError)
90+
if (result.IsError && String.IsNullOrWhiteSpace(result.Error))
9291
{
9392
result.Error = OidcConstants.TokenErrors.InvalidDPoPProof;
9493
}
@@ -117,21 +116,21 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP
117116
return Task.CompletedTask;
118117
}
119118

120-
if (!token.TryGetHeaderValue<string>("typ", out var typ) || typ != JwtClaimTypes.JwtTypes.DPoPProofToken)
119+
if (!token.TryGetHeaderValue<string>(JwtClaimTypes.TokenType, out var typ) || typ != JwtClaimTypes.JwtTypes.DPoPProofToken)
121120
{
122121
result.IsError = true;
123122
result.ErrorDescription = "Invalid 'typ' value.";
124123
return Task.CompletedTask;
125124
}
126125

127-
if (!token.TryGetHeaderValue<string>("alg", out var alg) || !SupportedDPoPSigningAlgorithms.Contains(alg))
126+
if (!token.TryGetHeaderValue<string>(JwtClaimTypes.Algorithm, out var alg) || !SupportedDPoPSigningAlgorithms.Contains(alg))
128127
{
129128
result.IsError = true;
130129
result.ErrorDescription = "Invalid 'alg' value.";
131130
return Task.CompletedTask;
132131
}
133132

134-
if (!token.TryGetHeaderValue<IDictionary<string, object>>(JwtClaimTypes.JsonWebKey, out var jwkValues))
133+
if (!token.TryGetHeaderValue<JsonElement>(JwtClaimTypes.JsonWebKey, out var jwkValues))
135134
{
136135
result.IsError = true;
137136
result.ErrorDescription = "Invalid 'jwk' value.";
@@ -170,7 +169,7 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP
170169
/// <summary>
171170
/// Validates the signature.
172171
/// </summary>
173-
protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result)
172+
protected virtual async Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result)
174173
{
175174
TokenValidationResult tokenValidationResult;
176175

@@ -186,27 +185,25 @@ protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context,
186185
};
187186

188187
var handler = new JsonWebTokenHandler();
189-
tokenValidationResult = handler.ValidateToken(context.ProofToken, tvp);
188+
tokenValidationResult = await handler.ValidateTokenAsync(context.ProofToken, tvp);
190189
}
191190
catch (Exception ex)
192191
{
193192
Logger.LogDebug("Error parsing DPoP token: {error}", ex.Message);
194193
result.IsError = true;
195194
result.ErrorDescription = "Invalid signature on DPoP token.";
196-
return Task.CompletedTask;
195+
return;
197196
}
198197

199198
if (tokenValidationResult.Exception != null)
200199
{
201200
Logger.LogDebug("Error parsing DPoP token: {error}", tokenValidationResult.Exception.Message);
202201
result.IsError = true;
203202
result.ErrorDescription = "Invalid signature on DPoP token.";
204-
return Task.CompletedTask;
203+
return;
205204
}
206205

207206
result.Payload = tokenValidationResult.Claims;
208-
209-
return Task.CompletedTask;
210207
}
211208

212209
/// <summary>
@@ -270,11 +267,11 @@ protected virtual async Task ValidatePayloadAsync(DPoPProofValidatonContext cont
270267
{
271268
if (iat is int)
272269
{
273-
result.IssuedAt = (int) iat;
270+
result.IssuedAt = (int)iat;
274271
}
275272
if (iat is long)
276273
{
277-
result.IssuedAt = (long) iat;
274+
result.IssuedAt = (long)iat;
278275
}
279276
}
280277

@@ -337,6 +334,9 @@ protected virtual async Task ValidateReplayAsync(DPoPProofValidatonContext conte
337334
// longer than the likelyhood of proof token expiration, which is done before replay
338335
skew *= 2;
339336
var cacheDuration = dpopOptions.ProofTokenValidityDuration + skew;
337+
338+
Logger.LogDebug("Adding proof token with jti {jti} to replay cache for duration {cacheDuration}", result.TokenId, cacheDuration);
339+
340340
await ReplayCache.AddAsync(ReplayCachePurpose, result.TokenId, DateTimeOffset.UtcNow.Add(cacheDuration));
341341
}
342342

@@ -447,11 +447,11 @@ protected virtual ValueTask<long> GetUnixTimeFromNonceAsync(DPoPProofValidatonCo
447447
return ValueTask.FromResult(iat);
448448
}
449449
}
450-
catch (Exception ex)
450+
catch(Exception ex)
451451
{
452452
Logger.LogDebug("Error parsing DPoP 'nonce' value: {error}", ex.ToString());
453453
}
454-
454+
455455
return ValueTask.FromResult<long>(0);
456456
}
457457

src/DPoP/DPoPServiceCollectionExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using ApiHost;
21
using Microsoft.AspNetCore.Authentication.JwtBearer;
32
using Microsoft.Extensions.DependencyInjection;
43
using Microsoft.Extensions.Options;

src/Duende.IdentityServer.Demo.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="Duende.IdentityServer" Version="6.3.5" />
10-
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
11-
<PackageReference Include="Serilog.AspNetCore" Version="6.0.0" />
9+
<PackageReference Include="Duende.IdentityServer" Version="7.0.1" />
10+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
11+
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
1212
</ItemGroup>
1313
</Project>
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
24
using Microsoft.AspNetCore.Mvc.RazorPages;
35

46
namespace IdentityServerHost.Pages.Account;
@@ -8,4 +10,4 @@ public class AccessDeniedModel : PageModel
810
public void OnGet()
911
{
1012
}
11-
}
13+
}

src/Pages/Account/Create/Index.cshtml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@page
2+
@model IdentityServerHost.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 IdentityServerHost.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+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace IdentityServerHost.Pages.Create;
7+
8+
public class InputModel
9+
{
10+
[Required]
11+
public string? Username { get; set; }
12+
13+
[Required]
14+
public string? Password { get; set; }
15+
16+
public string? Name { get; set; }
17+
public string? Email { get; set; }
18+
19+
public string? ReturnUrl { get; set; }
20+
21+
public string? Button { get; set; }
22+
}

0 commit comments

Comments
 (0)