-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathClientAssertionService.cs
More file actions
93 lines (83 loc) · 4.79 KB
/
ClientAssertionService.cs
File metadata and controls
93 lines (83 loc) · 4.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Copyright (c) Duende Software. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
namespace ClientAssertions;
/// <summary>
/// Creates signed client assertion JWTs (RFC 7523 / private_key_jwt).
/// Each call to <see cref="CreateAssertionAsync"/> produces a JWT with a fresh
/// <c>jti</c> and <c>iat</c>, which is critical when retries (e.g. DPoP nonce
/// challenges) require a new assertion to avoid replay rejection.
/// </summary>
public class ClientAssertionService
{
private readonly string _clientId;
private readonly string _audience;
private readonly SigningCredentials _signingCredentials;
public ClientAssertionService(string clientId, string audience, SigningCredentials signingCredentials)
{
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
_audience = audience ?? throw new ArgumentNullException(nameof(audience));
_signingCredentials = signingCredentials ?? throw new ArgumentNullException(nameof(signingCredentials));
}
/// <summary>
/// Creates a fresh <see cref="ClientAssertion"/> with a unique <c>jti</c>.
/// </summary>
public Task<ClientAssertion> CreateAssertionAsync()
{
var now = DateTime.UtcNow;
var descriptor = new SecurityTokenDescriptor
{
Issuer = _clientId,
Audience = _audience,
IssuedAt = now,
NotBefore = now,
Expires = now.AddMinutes(1),
SigningCredentials = _signingCredentials,
AdditionalHeaderClaims = new Dictionary<string, object>
{
{ "typ", "client-authentication+jwt" }
},
Claims = new Dictionary<string, object>
{
{ JwtClaimTypes.JwtId, Guid.NewGuid().ToString() },
{ JwtClaimTypes.Subject, _clientId },
}
};
var handler = new JsonWebTokenHandler();
var jwt = handler.CreateToken(descriptor);
return Task.FromResult(new ClientAssertion
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = jwt
});
}
/// <summary>
/// Creates signing credentials from the RSA key pair that matches the public
/// key registered for the "m2m.jwt" client at demo.duendesoftware.com.
/// In production, load the private key from a secure store (e.g. Azure Key Vault).
/// </summary>
public static SigningCredentials CreateSigningCredentials()
{
// This JWK contains the private key that corresponds to the public key
// registered at the demo IdentityServer for the "m2m.jwt" client.
var jwk = """
{
"d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ",
"dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE",
"dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M",
"e":"AQAB",
"kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA",
"kty":"RSA",
"n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw",
"p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE",
"q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts",
"qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4"
}
""";
var key = new JsonWebKey(jwk);
return new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
}
}