diff --git a/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md b/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md new file mode 100644 index 0000000000..73a0d7589c --- /dev/null +++ b/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md @@ -0,0 +1,129 @@ +# How to add a custom grant type in OpenIddict + +## ITokenExtensionGrant + +Create a class that inherits `ITokenExtensionGrant`, and then register it with the framework. + +In the `MyTokenExtensionGrant` class below we try to get the token details, The `ForbidResult` handles the failure case and `SignInResult` returns a new token response, You can pass more parameters to implement business checks. + +```cs +public class MyTokenExtensionGrant : ITokenExtensionGrant +{ + public const string ExtensionGrantName = "MyTokenExtensionGrant"; + + public string Name => ExtensionGrantName; + public async Task HandleAsync(ExtensionGrantContext context) + { + var userToken = context.Request.GetParameter("token").ToString(); + + if (string.IsNullOrEmpty(userToken)) + { + return new ForbidResult( + new[] {OpenIddictServerAspNetCoreDefaults.AuthenticationScheme}, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest + }!)); + } + + var transaction = await context.HttpContext.RequestServices.GetRequiredService().CreateTransactionAsync(); + transaction.EndpointType = OpenIddictServerEndpointType.Introspection; + transaction.Request = new OpenIddictRequest + { + ClientId = context.Request.ClientId, + ClientSecret = context.Request.ClientSecret, + Token = userToken + }; + + var notification = new OpenIddictServerEvents.ProcessAuthenticationContext(transaction); + var dispatcher = context.HttpContext.RequestServices.GetRequiredService(); + await dispatcher.DispatchAsync(notification); + + if (notification.IsRejected) + { + return new ForbidResult( + new []{ OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri + })); + } + + var principal = notification.GenericTokenPrincipal; + if (principal == null) + { + return new ForbidResult( + new []{ OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri + })); + } + + var userId = principal.FindUserId(); + var userManager = context.HttpContext.RequestServices.GetRequiredService(); + var user = await userManager.GetByIdAsync(userId.Value); + var userClaimsPrincipalFactory = context.HttpContext.RequestServices.GetRequiredService>(); + var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user); + claimsPrincipal.SetScopes(principal.GetScopes()); + claimsPrincipal.SetResources(await GetResourcesAsync(context, principal.GetScopes())); + await context.HttpContext.RequestServices.GetRequiredService().SetAsync(principal); + return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal); + } + + private async Task> GetResourcesAsync(ExtensionGrantContext context, ImmutableArray scopes) + { + var resources = new List(); + if (!scopes.Any()) + { + return resources; + } + + await foreach (var resource in context.HttpContext.RequestServices.GetRequiredService().ListResourcesAsync(scopes)) + { + resources.Add(resource); + } + return resources; + } +} +``` + +```cs +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + //... + PreConfigure(builder => + { + builder.Configure(openIddictServerOptions => + { + openIddictServerOptions.GrantTypes.Add(MyTokenExtensionGrant.ExtensionGrantName); + }); + }); + //... +} + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + //... + Configure(options => + { + options.Grants.Add(MyTokenExtensionGrant.ExtensionGrantName, new MyTokenExtensionGrant()); + }); + //... +} +``` + + +![Http request 1](postman1.png) + +![Http request 2](postman2.png) + +## Source code + +https://github.com/abpframework/abp/commit/3210f138454697647689b4868c8d4b7b3da02d44 + + diff --git a/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman1.png b/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman1.png new file mode 100644 index 0000000000..1e2c7535c5 Binary files /dev/null and b/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman1.png differ diff --git a/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman2.png b/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman2.png new file mode 100644 index 0000000000..c3dbb7324a Binary files /dev/null and b/docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman2.png differ diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs b/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs index bd94e16f9e..bf6a9cdb15 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs @@ -1,5 +1,6 @@ using System.Globalization; using OpenIddict.Abstractions; +using OpenIddict.Demo.Server.ExtensionGrants; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; @@ -75,6 +76,7 @@ public class ServerDataSeedContributor : IDataSeedContributor, ITransientDepende OpenIddictConstants.Permissions.GrantTypes.RefreshToken, OpenIddictConstants.Permissions.GrantTypes.DeviceCode, OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.Prefixes.GrantType + MyTokenExtensionGrant.ExtensionGrantName, OpenIddictConstants.Permissions.ResponseTypes.Code, OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken, diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/ExtensionGrants/MyTokenExtensionGrant.cs b/modules/openiddict/app/OpenIddict.Demo.Server/ExtensionGrants/MyTokenExtensionGrant.cs new file mode 100644 index 0000000000..62e4cba0b3 --- /dev/null +++ b/modules/openiddict/app/OpenIddict.Demo.Server/ExtensionGrants/MyTokenExtensionGrant.cs @@ -0,0 +1,99 @@ +using System.Collections.Immutable; +using System.Security.Principal; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using OpenIddict.Abstractions; +using OpenIddict.Server; +using OpenIddict.Server.AspNetCore; +using Volo.Abp.Identity; +using Volo.Abp.OpenIddict; +using Volo.Abp.OpenIddict.ExtensionGrantTypes; +using IdentityUser = Volo.Abp.Identity.IdentityUser; +using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult; + +namespace OpenIddict.Demo.Server.ExtensionGrants; + +public class MyTokenExtensionGrant : ITokenExtensionGrant +{ + public const string ExtensionGrantName = "MyTokenExtensionGrant"; + + public string Name => ExtensionGrantName; + public async Task HandleAsync(ExtensionGrantContext context) + { + var userToken = context.Request.GetParameter("token").ToString(); + + if (string.IsNullOrEmpty(userToken)) + { + return new ForbidResult( + new[] {OpenIddictServerAspNetCoreDefaults.AuthenticationScheme}, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest + }!)); + } + + var transaction = await context.HttpContext.RequestServices.GetRequiredService().CreateTransactionAsync(); + transaction.EndpointType = OpenIddictServerEndpointType.Introspection; + transaction.Request = new OpenIddictRequest + { + ClientId = context.Request.ClientId, + ClientSecret = context.Request.ClientSecret, + Token = userToken + }; + + var notification = new OpenIddictServerEvents.ProcessAuthenticationContext(transaction); + var dispatcher = context.HttpContext.RequestServices.GetRequiredService(); + await dispatcher.DispatchAsync(notification); + + if (notification.IsRejected) + { + return new ForbidResult( + new []{ OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri + })); + } + + var principal = notification.GenericTokenPrincipal; + if (principal == null) + { + return new ForbidResult( + new []{ OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = notification.Error ?? OpenIddictConstants.Errors.InvalidRequest, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = notification.ErrorDescription, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = notification.ErrorUri + })); + } + + var userId = principal.FindUserId(); + var userManager = context.HttpContext.RequestServices.GetRequiredService(); + var user = await userManager.GetByIdAsync(userId.Value); + var userClaimsPrincipalFactory = context.HttpContext.RequestServices.GetRequiredService>(); + var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user); + claimsPrincipal.SetScopes(principal.GetScopes()); + claimsPrincipal.SetResources(await GetResourcesAsync(context, principal.GetScopes())); + await context.HttpContext.RequestServices.GetRequiredService().SetAsync(principal); + return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal); + } + + private async Task> GetResourcesAsync(ExtensionGrantContext context, ImmutableArray scopes) + { + var resources = new List(); + if (!scopes.Any()) + { + return resources; + } + + await foreach (var resource in context.HttpContext.RequestServices.GetRequiredService().ListResourcesAsync(scopes)) + { + resources.Add(resource); + } + return resources; + } +} diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs index 51a2dc81a9..9b20fc9674 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs @@ -2,6 +2,7 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.EntityFrameworkCore; using OpenIddict.Demo.Server.EntityFrameworkCore; +using OpenIddict.Demo.Server.ExtensionGrants; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Web; @@ -21,6 +22,7 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.OpenIddict; using Volo.Abp.OpenIddict.EntityFrameworkCore; +using Volo.Abp.OpenIddict.ExtensionGrantTypes; using Volo.Abp.OpenIddict.WildcardDomains; using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement.EntityFrameworkCore; @@ -104,6 +106,11 @@ public class OpenIddictServerModule : AbpModule var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); builder.AddEncryptionCertificate(certificate); } + + builder.Configure(openIddictServerOptions => + { + openIddictServerOptions.GrantTypes.Add(MyTokenExtensionGrant.ExtensionGrantName); + }); }); PreConfigure(options => @@ -141,6 +148,11 @@ public class OpenIddictServerModule : AbpModule { options.IsEnabled = true; }); + + Configure(options => + { + options.Grants.Add(MyTokenExtensionGrant.ExtensionGrantName, new MyTokenExtensionGrant()); + }); } public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)