openiddict-samples
openiddict-samples copied to clipboard
Any samples for grant type client credentials and client assertions?
Confirm you've already contributed to this project or that you sponsor it
- [X] I confirm I'm a sponsor or a contributor
Version
3.x
Question
We are testing a Smart FHIR implementation with openiddict. I am stuck on the client assertion flow and would request any links to documentation or samples that implement or can help implement client assertion and client credentials.
private_key_jwt
is already supported by the new OpenIddict client as it was required for the Apple SignIn provider but the OpenIddict server doesn't support it yet. Native support for client assertions is tracked by https://github.com/openiddict/openiddict-core/issues/1251.
If it's important for your company, let me know, it's something we can likely fund and implement for 4.0.
It would definitely be of great value to anyone who is using Openiddict to comply with FHIR and OAUTH2 requirements. Assertion grant flows are 1 of the 4 vignettes that are tested. I will look at the samples and define a custom flow that handles assertion grants (I saw your comment about this flow technically being similar to the password flow). We are currently going through the certification process and this is the last hurdle.
Yes adding it to 4.0 would help.
Assertion grant flows are 1 of the 4 vignettes that are tested. I will look at the samples and define a custom flow that handles assertion grants (I saw your comment about this flow technically being similar to the password flow).
It's worth noting that client assertions != assertion grants: the former are generated to serve as client credentials while the second are generated to serve as an entity identity (typically a user).
Noted. Thanks for this elaboration.
How do I override the default client credentials validation so that it allows to check for client assertions?
From: Kévin Chalet @.> Sent: Saturday, May 28, 2022 4:17 PM To: openiddict/openiddict-samples @.> Cc: svasui123 @.>; Author @.> Subject: Re: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
Assertion grant flows are 1 of the 4 vignettes that are tested. I will look at the samples and define a custom flow that handles assertion grants (I saw your comment about this flow technically being similar to the password flow).
It's worth noting that client assertions != assertion grants: the former are generated to serve as client credentials while the second are generated to serve as an entity identity (typically a user).
— Reply to this email directly, view it on GitHub https://github.com/openiddict/openiddict-samples/issues/193#issuecomment-1140324185 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AZDMOETU4B7MJSNIZPVZ2MLVMJ5LJANCNFSM5XCFPXCA . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AZDMOEVJCEMGWSTCU4IOT73VMJ5LJA5CNFSM5XCFPXCKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOIP37OWI.gif Message ID: @.*** @.***> >
If you can't/don't want to wait for built-in support, you'll need to replace these 2 handlers:
-
https://github.com/openiddict/openiddict-core/blob/eb8715d96440c05400b3613d37ca64385d79c042/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs#L498-L537:
ValidateClientCredentialsParameters
blocks the request ifclient_id
and/orclient_secret
is/are missing. You'll want to replace it by an equivalent handler that supports theclient_assertion
/client_assertion_type
parameters. -
https://github.com/openiddict/openiddict-core/blob/eb8715d96440c05400b3613d37ca64385d79c042/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs#L928-L986:
ValidateClientSecret
only works with aclient_secret
so you'll need a custom handler that usesclient_assertion
/client_assertion_type
.
It's not mandatory but you'll probably want to tweak the configuration document to indicate your server instance supports client assertions. For that, you'll need to add private_key_jwt
(assuming you're using asymmetric signing keys) to HandleConfigurationRequestContext.TokenEndpointAuthenticationMethods
.
See https://github.com/openiddict/openiddict-core/blob/eb8715d96440c05400b3613d37ca64385d79c042/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs#L491-L535 for the handler responsible for adding client_secret_basic
/client_secret_post
.
We will use the built-in support as soon as it is available.
Having said that I think working with these handlers would help me better understand this extremely well done package.
From: Kévin Chalet @.> Sent: Wednesday, June 1, 2022 12:23 PM To: openiddict/openiddict-samples @.> Cc: svasui123 @.>; Author @.> Subject: Re: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
If you can't/don't want to wait for built-in support, you'll need to replace these 2 handlers:
- https://github.com/openiddict/openiddict-core/blob/eb8715d96440c05400b3613d37ca64385d79c042/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs#L498-L537: ValidateClientCredentialsParameters blocks the request if client_id and/or client_secret is/are missing. You'll want to replace it by an equivalent handler that supports the client_assertion/client_assertion_type parameters.
- https://github.com/openiddict/openiddict-core/blob/eb8715d96440c05400b3613d37ca64385d79c042/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs#L928-L986: ValidateClientSecret only works with a client_secret so you'll need a custom handler that uses client_assertion/client_assertion_type.
It's not mandatory but you'll probably want to tweak the configuration document to indicate your server instance supports client assertions. For that, you'll need to add private_key_jwt (assuming you're using asymmetric signing keys) to HandleConfigurationRequestContext.TokenEndpointAuthenticationMethods.
See https://github.com/openiddict/openiddict-core/blob/eb8715d96440c05400b3613d37ca64385d79c042/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs#L491-L535 for the handler responsible for adding client_secret_basic/client_secret_post.
— Reply to this email directly, view it on GitHub https://github.com/openiddict/openiddict-samples/issues/193#issuecomment-1143827420 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AZDMOEUM5VE2SHDGE57MLADVM6FABANCNFSM5XCFPXCA . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AZDMOEVOP6XT7CTTGURZJEDVM6FABA5CNFSM5XCFPXCKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOIQWWXXA.gif Message ID: @.*** @.***> >
I followed the instructions given in https://github.com/openiddict/openiddict-core/issues/736 to wire up an event handler. When I try to add options.AddEventHandler<HandleConfigurationRequestContext, MyEventHandler> (); under AddServer it is giving me an error. What is the new syntax?
Having said that I think working with these handlers would help me better understand this extremely well done package.
Haha, thanks for the kind words! 😃
What is the new syntax?
I'd recommend using the same approach as the one used for the built-in handlers:
public class YourCustomHandler : IOpenIddictServerHandler<...>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<...>()
.UseSingletonHandler<YourCustomHandler>()
.SetOrder(...)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(... context)
{
...
}
}
options.AddEventHandler(YourCustomHandler.Descriptor);
Alternatively, you can also use inline handlers, as shown in https://documentation.openiddict.com/guides/index.html#events-model.
Hi,
I am able to override the handlers for ValidateClientCredentialsParameters and the AttachClientAuthenticationMethods. When I try to override the ValidAteCLientSecret I get run time error:
System.InvalidOperationException: 'The core services must be registered when enabling the OpenIddict server feature. To register the OpenIddict core services, reference the 'OpenIddict.Core' package and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.
My Program.cs
builder.Services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Check if next line fixes the error.
options.SetDefaultApplicationEntity<OpenIddictEntityFrameworkCoreApplication>();
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
;
// Enable Quartz.NET integration.
//options.UseQuartz();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the authorization, logout, token and userinfo endpoints.
options.AllowClientCredentialsFlow();
options.SetTokenEndpointUris(builder.Configuration["OpenIddict:Endpoints:Token"]);
Console.WriteLine("Config:" + builder.Configuration["OpenIddict:Endpoints:Token"]);
// to enable revoke tokens
// Encryption and signing of tokens
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption()
;
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess, Scopes.OpenId, "scp:fhirUser", "scp:patient/Patient.read");
//options.RegisterClaims(Claims.Subject, Claims.Name, Claims.Username, "fhirUser");
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableTokenEndpointPassthrough()
;
options.AddEventHandler(MyValidateClientCredentialsParameters.Descriptor);
options.AddEventHandler(MyValidateClientSecret.Descriptor);
options.AddEventHandler(MyAttachClientAuthenticationMethods.Descriptor);
}) // Register the OpenIddict validation components.
.AddValidation(options =>
{
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
//to cater to immediate access token revocation
options.EnableTokenEntryValidation();
});
Code where it crashes:
public class MyValidateClientSecret : ValidateClientSecret, IOpenIddictServerHandler<ValidateTokenRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public MyValidateClientSecret() => throw new InvalidOperationException(OpenIddictResources.GetResourceString(OpenIddictResources.ID0016));
public MyValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
Crashes on the above line
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// Turn off RequireCLientId
/// </summary>
public static new OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
//.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<MyValidateClientSecret>()
.SetOrder(OpenIddictServerHandlers.Authentication.ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
What am I missing?
Best Regards
Vasu
From: Kévin Chalet @.> Sent: Thursday, June 2, 2022 4:02 AM To: openiddict/openiddict-samples @.> Cc: svasui123 @.>; Author @.> Subject: Re: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
Having said that I think working with these handlers would help me better understand this extremely well done package.
Haha, thanks for the kind words! 😃
What is the new syntax?
I'd recommend using the same approach as the one used for the built-in handlers:
public class YourCustomHandler : IOpenIddictServerHandler<...>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<...>()
.UseSingletonHandler<YourCustomHandler>()
.SetOrder(...)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(... context)
{
...
}
} options.AddEventHandler(YourCustomHandler.Descriptor);
Alternatively, you can also use inline handlers, as shown in https://documentation.openiddict.com/guides/index.html#events-model.
— Reply to this email directly, view it on GitHub https://github.com/openiddict/openiddict-samples/issues/193#issuecomment-1144568485 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AZDMOESTNAI6CTEC3H27PWLVNBTARANCNFSM5XCFPXCA . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AZDMOEQJJLQOB2QKXEA3GWLVNBTARA5CNFSM5XCFPXCKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOIQ4LVJI.gif Message ID: @.*** @.***> >
I suspect this could be related to the fact you're trying to subclass the built-in ValidateClientSecret
handler, which is something you should never do.
Instead, create your own independent handler - that don't subclass ValidateClientSecret
- and remove the built-in one by doing options.RemoveEventHandler(ValidateClientSecret.Descriptor)
.
Hi Kevin,
This is what I ended up doing. I am currently working on adding an additional table to verify the jwksurl and store the public keys (if jwks url is not given then it will default to the stored key .. this is not recommended but is a requirement). Also messages are hard coded since we first wanted to get a working model and the documentation URI are incorrect.
Best Regards
Vasu
From: @.*** @.> Sent: Sunday, June 5, 2022 6:09 PM To: 'openiddict/openiddict-samples' @.>; 'openiddict/openiddict-samples' @.> Cc: 'Author' @.> Subject: RE: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
Thank you for your prompt response.
I was actually overriding the method by subclassing and using new to override.
I removed the subclassing and removed the built-in handlers. I am now able to see the flow into the custom handlers after overriding ValidateClientIdParameter to allow requests with grant type client credentials and clientassertion and clientassertiontype in the request.
I will keep you posted on the progress and share the code after implementing the client-assertion requirements.
From: Kévin Chalet @.*** @.> > Sent: Sunday, June 5, 2022 2:40 PM To: openiddict/openiddict-samples @. @.> > Cc: svasui123 @. @.> >; Author @. @.***> > Subject: Re: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
I suspect this could be related to the fact you're trying to subclass the built-in ValidateClientSecret handler, which is something you should never do.
Instead, create your own independent handler - that don't subclass ValidateClientSecret - and remove the built-in one by doing options.RemoveEventHandler(ValidateClientSecret.Descriptor).
— Reply to this email directly, view it on GitHub https://github.com/openiddict/openiddict-samples/issues/193#issuecomment-1146863255 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AZDMOESFQM6ZZPQ4RU27K7LVNTYAFANCNFSM5XCFPXCA . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AZDMOEUVICWKORB42SFUH6LVNTYAFA5CNFSM5XCFPXCKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOIRN35FY.gif Message ID: @.*** @.***> >
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.EntityFrameworkCore; using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.Validation.AspNetCore; using SampleOverride; using SampleOverride.Data; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerHandlers.Exchange; using static SampleOverride.MyEventHandler;
var builder = WebApplication.CreateBuilder(args); //Add compression builder.Services.Configure<GzipCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Fastest); builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/fhir+xml", "application/fhir+json" }); options.Providers.Add<GzipCompressionProvider>(); }); // Add services to the container. var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<ApplicationDbContext>(options => { // Configure the context to use Microsoft SQL Server. options.UseSqlServer(connectionString);
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
}); builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // Tell the Net stack to only use TLS1.2 //System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
//builder.Services.Configure<IdentityOptions>(options => //{ // // Configure Identity to use the same JWT claims as OpenIddict instead // // of the legacy WS-Federation claims it uses by default (ClaimTypes), // // which saves you from doing the mapping in your authorization controller. // options.ClaimsIdentity.UserNameClaimType = Claims.Name; // options.ClaimsIdentity.UserIdClaimType = Claims.Subject; // options.ClaimsIdentity.RoleClaimType = Claims.Role; // options.ClaimsIdentity.EmailClaimType = Claims.Email;
// options.SignIn.RequireConfirmedAccount = false; //});
builder.Services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
options.SetDefaultApplicationEntity<OpenIddictEntityFrameworkCoreApplication>();
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
;
// Enable Quartz.NET integration.
//options.UseQuartz();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the authorization, logout, token and userinfo endpoints.
options.AllowClientCredentialsFlow();
options.SetTokenEndpointUris(builder.Configuration["OpenIddict:Endpoints:Token"]);
Console.WriteLine("Config:" + builder.Configuration["OpenIddict:Endpoints:Token"]);
// to enable revoke tokens
// Encryption and signing of tokens
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption()
;
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess, Scopes.OpenId, "scp:fhirUser", "scp:patient/Patient.read");
//options.RegisterClaims(Claims.Subject, Claims.Name, Claims.Username, "fhirUser");
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableTokenEndpointPassthrough()
;
options.RemoveEventHandler(OpenIddict.Server.OpenIddictServerHandlers.Exchange.ValidateClientIdParameter.Descriptor);
options.RemoveEventHandler(OpenIddict.Server.OpenIddictServerHandlers.Exchange.ValidateClientCredentialsParameters.Descriptor);
options.RemoveEventHandler(OpenIddict.Server.OpenIddictServerHandlers.Exchange.ValidateClientSecret.Descriptor);
options.AddEventHandler(MyEventHandler.MyValidateClientIdParameter.Descriptor);
options.AddEventHandler(MyEventHandler.MyValidateClientCredentialsParameters.Descriptor);
options.AddEventHandler(MyEventHandler.MyValidateClientSecret.Descriptor);
options.AddEventHandler(MyAttachClientAuthenticationMethods.Descriptor);
}) // Register the OpenIddict validation components.
.AddValidation(options =>
{
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
//to cater to immediate access token revocation
options.EnableTokenEntryValidation();
});
builder.Services.AddRazorPages();
builder.Services.AddHostedService<SampleOverride.Worker>(); builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
var app = builder.Build();
// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); }
app.UseHttpsRedirection(); app.UseStaticFiles();
app.UseRouting(); app.UseAuthentication(); app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); });
app.MapRazorPages();
app.Run();
using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; using OpenIddict.Server; using System.Diagnostics; using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Headers; using System.Security.Cryptography; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using static OpenIddict.Server.OpenIddictServerHandlers.Discovery; using static OpenIddict.Server.OpenIddictServerHandlers.Exchange;
namespace SampleOverride
{
public class MyEventHandler
{
static string issval = string.Empty;
public class MyAttachClientAuthenticationMethods : IOpenIddictServerHandler<HandleConfigurationRequestContext>
{
///
/// <inheritdoc/>
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.IntrospectionEndpoint is not null)
{
context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost);
}
if (context.RevocationEndpoint is not null)
{
context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost);
}
if (context.TokenEndpoint is not null)
{
context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost);
context.TokenEndpointAuthenticationMethods.Add("private_key_jwt");
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class MyValidateClientSecret : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public MyValidateClientSecret() => throw new InvalidOperationException(OpenIddictResources.GetResourceString(OpenIddictResources.ID0016));
public MyValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// Turn off RequireCLientId
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
//.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<MyValidateClientSecret>()
.SetOrder(OpenIddictServerHandlers.Authentication.ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
{
Console.WriteLine("In custom validate token");
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
//vasu comment starts
if (!string.IsNullOrEmpty(context.Request.ClientAssertion))
{
//get the iss from the assertion
//verify if clientid exists.. if yes continue otherwise return error
if (!string.IsNullOrEmpty(issval))
{
var applicationn = await _applicationManager.FindByClientIdAsync(issval) ??
throw new InvalidOperationException(OpenIddictResources.GetResourceString(OpenIddictResources.ID0032));
}
return;
};
//vasu changes end
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), OpenIddictResources.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(OpenIddictResources.GetResourceString(OpenIddictResources.ID0032));
// If the application is a public client, don't validate the client secret.
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), OpenIddict.Abstractions.OpenIddictResources.FormatID4000(Parameters.ClientSecret));
if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
{
context.Logger.LogInformation(OpenIddict.Abstractions.OpenIddictResources.GetResourceString(OpenIddict.Abstractions.OpenIddictResources.ID6085), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: OpenIddict.Abstractions.OpenIddictResources.GetResourceString(OpenIddict.Abstractions.OpenIddictResources.ID2055),
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2055));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests that don't
/// specify client credentials for the client credentials grant type.
/// </summary>
public class MyValidateClientCredentialsParameters : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.UseSingletonHandler<MyValidateClientCredentialsParameters>()
.SetOrder(ValidateAuthorizationCodeParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
//to be done
Console.WriteLine("In custom validation");
//check if client assertion and client credentials
if (context.Request.IsClientCredentialsGrantType())
{
if (!string.IsNullOrEmpty(context.Request.ClientAssertion) && !string.IsNullOrEmpty(context.Request.ClientAssertionType))
{
if (context.Request.ClientAssertionType != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
{
context.Reject(
error: Errors.InvalidRequest,
description: "Client Assertion Type is not valid",
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2057));
return default;
};
//base64decode the client assertion
//if the jwks_url is not given then use public key stored in db .. to implement
//if the jwks_url is given, get the public key for the given keyid in the client assertion
string tokenStr = context.Request.ClientAssertion;
string[] tokenParts = tokenStr.Split('.');
if (tokenParts.Length != 3)
{
context.Reject(
error: Errors.InvalidRequest,
description: "Client Assertion is not valid",
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2057));
return default;
};
Dictionary<string, string> Tags = new Dictionary<string, string>();
Tags = GetTokenInfo(context.Request.ClientAssertion);
issval = Tags.FirstOrDefault(x => x.Key == "iss").Value;
var handler = new JwtSecurityTokenHandler();
if (!handler.CanReadToken(issval))
{
context.Reject(
error: Errors.InvalidRequest,
description: "Iss is not valid",
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2057));
return default;
};
//UpdateAppSetting.UpdateJson(issval);
var subval = Tags.FirstOrDefault(x => x.Key == "sub").Value;
if (issval != subval)
{
context.Reject(
error: Errors.InvalidRequest,
description: "Iss and Sub are not the same",
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2057));
return default;
};
Tags = GetTokenInfo(issval);
var jwksuri = Tags.FirstOrDefault(x => x.Key == "jwks_url").Value;
HttpResponseMessage response;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(jwksuri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//GET Method
response = client.GetAsync(jwksuri, CancellationToken.None).Result;
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Internal server Error");
}
}
var jsoncontent = response.Content;
Dictionary<string, string> dictval;
var data = jsoncontent.ReadAsStringAsync().Result;
//.ToString();
var jwk = JsonWebKeySet.Create(data);
var seckeys = new List<SecurityKey>();
seckeys = GetSecurityKeys(jwk);
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
//this should be the clientid
ValidIssuer = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJlZ2lzdHJhdGlvbi10b2tlbiJ9.eyJqd2tzX3VybCI6Imh0dHA6Ly8xMC4xNS4yNTIuNzMvaW5mZXJuby8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJhY2Nlc3NUb2tlbnNFeHBpcmVJbiI6MTUsImlhdCI6MTU5NzQxMzE5NX0.q4v4Msc74kN506KTZ0q_minyapJw0gwlT6M_uiL73S4",
//this hsould be our end point for the
ValidAudience = "https://localost:7048/bulk-token",
IssuerSigningKeys = seckeys,
CryptoProviderFactory = new CryptoProviderFactory()
{
CacheSignatureProviders = false
}
};
try
{
handler = new JwtSecurityTokenHandler();
handler.ValidateToken(tokenStr, validationParameters, out var validatedSecurityToken);
var jwtSecurityToken = handler.ReadJwtToken(tokenStr);
var TokenInfo = new Dictionary<string, string>();
var claims = jwtSecurityToken.Claims.ToList();
foreach (var claim in claims)
{
TokenInfo.Add(claim.Type, claim.Value);
Console.WriteLine(claim.Type + " : " + claim.Value);
}
}
catch (SecurityTokenInvalidIssuerException issex)
{
Console.WriteLine(issex.Message.ToString());
Console.WriteLine("Issuer error");
}
catch (Exception ex)
{
//switch
// case when ex.Message.Contains("IDX10516")
// : Console.WriteLine(ex.Message.ToString());
//default:
Console.WriteLine(ex.Message.ToString());
Console.WriteLine("sign error");
}
return default;
}
}
//if client assetion, get the payolad and get the aud or sub for the client_id
//set the client_id
//continue to next step
// Reject grant_type=client_credentials requests missing the client credentials.
// See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
if (context.Request.IsClientCredentialsGrantType() && (string.IsNullOrEmpty(context.Request.ClientId) ||
string.IsNullOrEmpty(context.Request.ClientSecret)))
{
context.Reject(
error: Errors.InvalidRequest,
description: OpenIddict.Abstractions.OpenIddictResources.FormatID2057(Parameters.ClientId, Parameters.ClientSecret),
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2057));
return default;
}
return default;
}
public static byte[] FromBase64Url(string base64Url)
{
string padded = base64Url.Length % 4 == 0
? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
string base64 = padded.Replace("_", "/")
.Replace("-", "+");
return Convert.FromBase64String(base64);
}
private static Dictionary<string, string> GetTokenInfo(string token)
{
var TokenInfo = new Dictionary<string, string>();
var handler = new JwtSecurityTokenHandler();
//check for valid token true or false
if (handler.CanReadToken(token))
{
var jwtSecurityToken = handler.ReadJwtToken(token);
var claims = jwtSecurityToken.Claims.ToList();
foreach (var claim in claims)
{
TokenInfo.Add(claim.Type, claim.Value);
}
}
return TokenInfo;
}
private List<SecurityKey> GetSecurityKeys(JsonWebKeySet jsonWebKeySet)
{
var keys = new List<SecurityKey>();
foreach (var key in jsonWebKeySet.Keys)
{
if (key.Kty == "EC")
{
ECDsa eckey = ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP384,
Q = new ECPoint
{
X = FromBase64Url(key.X),
Y = FromBase64Url(key.Y)
}
});
var eckeys = new ECDsaSecurityKey(eckey)
{
KeyId = key.Kid
};
keys.Add(eckeys);
}
else if (key.Kty == "RSA")
{
var e = FromBase64Url(key.E);
var n = FromBase64Url(key.N);
var rsakey = new RsaSecurityKey(new RSAParameters { Exponent = e, Modulus = n })
{
KeyId = key.Kid
};
keys.Add(rsakey);
}
else
{
throw new NotImplementedException("Unknown key type for token validation");
}
}
return keys;
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests that don't specify a client identifier.
/// </summary>
public class MyValidateClientIdParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.UseSingletonHandler<MyValidateClientIdParameter>()
.SetOrder(ValidateGrantType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.Custom)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenRequestContext context)
{
Console.WriteLine("In custom validate clientid");
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
//vsi added flow to allow client assertion and grant type client_credentials
if (context.Request.IsClientCredentialsGrantType() && (!string.IsNullOrEmpty(context.Request.ClientAssertion) && !string.IsNullOrEmpty(context.Request.ClientAssertionType)))
{
return default;
}
if (!string.IsNullOrEmpty(context.ClientId))
{
return default;
}
// At this stage, reject the token request unless the client identification requirement was disabled.
// Independently of this setting, also reject grant_type=authorization_code requests that don't specify
// a client_id, as the client identifier MUST be sent by the client application in the request body
// if it cannot be inferred from the client authentication method (e.g the username when using basic).
// See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information.
if (!context.Options.AcceptAnonymousClients || context.Request.IsAuthorizationCodeGrantType())
{
context.Logger.LogInformation(OpenIddict.Abstractions.OpenIddictResources.GetResourceString(OpenIddict.Abstractions.OpenIddictResources.ID6077), Parameters.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: OpenIddict.Abstractions.OpenIddictResources.FormatID2029(Parameters.ClientId),
uri: OpenIddict.Abstractions.OpenIddictResources.FormatID8000(OpenIddict.Abstractions.OpenIddictResources.ID2029));
return default;
}
return default;
}
}
}
}
Hi Kevin,
Is there a new version with the private_key_jwt included?
Best Regards
Vasu
From: Kévin Chalet @.> Sent: Monday, August 22, 2022 6:47 PM To: openiddict/openiddict-samples @.> Cc: svasui123 @.>; Author @.> Subject: Re: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
Closed #193 https://github.com/openiddict/openiddict-samples/issues/193 as completed.
— Reply to this email directly, view it on GitHub https://github.com/openiddict/openiddict-samples/issues/193#event-7236846555 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AZDMOETISCTRXYUT2QEPJE3V2P7PVANCNFSM5XCFPXCA . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AZDMOETMBLMWZY7DB3JDUG3V2P7PVA5CNFSM5XCFPXCKYY3PNVWWK3TUL52HS4DFWZEXG43VMVCXMZLOORHG65DJMZUWGYLUNFXW5KTDN5WW2ZLOORPWSZGPAAAAAANPLGB5W.gif Message ID: @.*** @.***> >
Hey Vasu,
Support for client authentication assertions in the server stack is tracked by https://github.com/openiddict/openiddict-core/issues/1251. 4.0 (whose 3rd preview shipped yesterday) will mostly focus on the whole new client stack so it's likely something that will need to wait a bit (maybe 5.0 or 6.0?).
Cheers.
For the record, client assertions support is part of the 5.0 preview1 release that shipped today: https://kevinchalet.com/2023/10/20/introducing-native-applications-per-client-token-lifetimes-and-client-assertions-support-in-openiddict-5-0-preview1/
Thank you. We will check this.
From: Kévin Chalet @.> Sent: Friday, October 20, 2023 3:59 PM To: openiddict/openiddict-samples @.> Cc: svasui123 @.>; Author @.> Subject: Re: [openiddict/openiddict-samples] Any samples for grant type client credentials and client assertions? (Issue #193)
For the record, client assertions support is part of the 5.0 preview1 release that shipped today: https://kevinchalet.com/2023/10/20/introducing-native-applications-per-client-token-lifetimes-and-client-assertions-support-in-openiddict-5-0-preview1/
— Reply to this email directly, view it on GitHub https://github.com/openiddict/openiddict-samples/issues/193#issuecomment-1773322749 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AZDMOERH4TZW3UD3HMNPRN3YALJZPAVCNFSM5XCFPXCKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCNZXGMZTEMRXGQ4Q . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AZDMOEX63XQOO442IXKR26TYALJZPA5CNFSM5XCFPXCKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGONGZMD7I.gif Message ID: @.***>