Skip to content

Commit

Permalink
Merge pull request #422 from IdentityModel/joe/client-assertion-event
Browse files Browse the repository at this point in the history
Add function to options to create client assertions dynamically
  • Loading branch information
brockallen authored Apr 3, 2024
2 parents eea4dd9 + b1605d9 commit dfb8097
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 5 deletions.
56 changes: 53 additions & 3 deletions clients/ConsoleClientWithBrowser/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Serilog.Sinks.SystemConsole.Themes;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using IdentityModel;
using Microsoft.IdentityModel.Tokens;
using IdentityModel.Client;

namespace ConsoleClientWithBrowser
{
Expand All @@ -29,17 +34,62 @@ public static async Task Main()
await SignIn();
}

private static string rsaKey =
"{" +
"\"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\"" +
"}";

private static string CreateClientToken(SigningCredentials credential, string clientId, string audience)
{
var now = DateTime.UtcNow;

var token = new JwtSecurityToken(
clientId,
audience,
new List<Claim>()
{
new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()),
new Claim(JwtClaimTypes.Subject, clientId),
new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64)
},
now,
now.AddMinutes(1),
credential
);

var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}

private static async Task SignIn()
{
// create a redirect URI using an available port on the loopback address.
// requires the OP to allow random ports on 127.0.0.1 - otherwise set a static port
var browser = new SystemBrowser();
string redirectUri = string.Format($"http://127.0.0.1:{browser.Port}");
var redirectUri = string.Format($"http://127.0.0.1:{browser.Port}");
var authority = "https://demo.duendesoftware.com";

var jwk = new JsonWebKey(rsaKey);
var credential = new SigningCredentials(jwk, "RS256");

var options = new OidcClientOptions
{
Authority = "https://demo.duendesoftware.com",
ClientId = "interactive.public.short",
Authority = authority,
ClientId = "interactive.confidential.short.jwt",
GetClientAssertionAsync = () => Task.FromResult(new ClientAssertion
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = CreateClientToken(credential, "interactive.confidential.short.jwt", authority)
}),
RedirectUri = redirectUri,
Scope = "openid profile api offline_access",
FilterClaims = false,
Expand Down
2 changes: 1 addition & 1 deletion src/OidcClient/OidcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ public virtual async Task<RefreshTokenResult> RefreshTokenAsync(
Address = Options.ProviderInformation.TokenEndpoint,
ClientId = Options.ClientId,
ClientSecret = Options.ClientSecret,
ClientAssertion = Options.ClientAssertion,
ClientAssertion = await Options.GetClientAssertionAsync(),
ClientCredentialStyle = Options.TokenClientCredentialStyle,
RefreshToken = refreshToken,
Parameters = backChannelParameters,
Expand Down
16 changes: 16 additions & 0 deletions src/OidcClient/OidcClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.Net.Http;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging.Abstractions;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace IdentityModel.OidcClient
{
Expand All @@ -18,6 +20,14 @@ namespace IdentityModel.OidcClient
/// </summary>
public class OidcClientOptions
{
/// <summary>
/// Creates an instance of the OidcClientOptions class.
/// </summary>
public OidcClientOptions()
{
GetClientAssertionAsync ??= () => Task.FromResult(ClientAssertion);
}

/// <summary>
/// Gets or sets the authority.
/// </summary>
Expand Down Expand Up @@ -58,6 +68,12 @@ public class OidcClientOptions
/// </value>
public ClientAssertion ClientAssertion { get; set; } = new ClientAssertion();

/// <summary>
/// Gets or sets a callback that computes the client assertion. By default, this returns the statically configured ClientAssertion
/// </summary>
[JsonIgnore]
public Func<Task<ClientAssertion>> GetClientAssertionAsync { get; set; }

/// <summary>
/// Gets or sets the scopes (required).
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/OidcClient/ResponseProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private async Task<TokenResponse> RedeemCodeAsync(string code, AuthorizeState st

ClientId = _options.ClientId,
ClientSecret = _options.ClientSecret,
ClientAssertion = _options.ClientAssertion,
ClientAssertion = await _options.GetClientAssertionAsync(),
ClientCredentialStyle = _options.TokenClientCredentialStyle,

Code = code,
Expand Down
14 changes: 14 additions & 0 deletions test/OidcClient.Tests/ConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using FluentAssertions;
using IdentityModel.Client;
using IdentityModel.Jwk;
using IdentityModel.OidcClient.Tests.Infrastructure;
using System;
Expand Down Expand Up @@ -173,5 +174,18 @@ public void Error401_while_loading_discovery_document_should_throw()

act.Should().Throw<InvalidOperationException>().Where(e => e.Message.Equals("Error loading discovery document: Error connecting to https://authority/.well-known/openid-configuration: not found"));
}

[Fact]
public async Task GetClientAssertionAsync_should_return_statically_configured_client_assertion_by_default()
{
var options = new OidcClientOptions
{
ClientAssertion = new ClientAssertion { Type = "test", Value = "expected" }
};

var result = await options.GetClientAssertionAsync();
result.Type.Should().Be("test");
result.Value.Should().Be("expected");
}
}
}

0 comments on commit dfb8097

Please sign in to comment.