Skip to content

Commit

Permalink
Merge pull request #6902 from aspnetboilerplate/feat/6803
Browse files Browse the repository at this point in the history
Added OpenIddict integration packages
  • Loading branch information
ismcagdas committed Mar 5, 2024
2 parents 212e7cc + 116f6e1 commit 8658bcf
Show file tree
Hide file tree
Showing 61 changed files with 5,441 additions and 1 deletion.
21 changes: 21 additions & 0 deletions Abp.sln
Expand Up @@ -191,6 +191,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.FileSystem.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.Azure.Tests", "test\Abp.BlobStoring.Azure.Tests\Abp.BlobStoring.Azure.Tests.csproj", "{168BE615-DA0F-42FA-98F4-6694034DBF8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.ZeroCore.OpenIddict", "src\Abp.ZeroCore.OpenIddict\Abp.ZeroCore.OpenIddict.csproj", "{0C3B9DEA-FC31-4039-ABB1-A087BB452B49}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.ZeroCore.OpenIddict.EntityFrameworkCore", "src\Abp.ZeroCore.OpenIddict.EntityFrameworkCore\Abp.ZeroCore.OpenIddict.EntityFrameworkCore.csproj", "{D4B74841-E40B-4856-92B4-BD98FB083DD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.AspNetCore.OpenIddict", "src\Abp.AspNetCore.OpenIddict\Abp.AspNetCore.OpenIddict.csproj", "{21B57D41-646E-43E3-B146-15F7CDE92A5E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -535,6 +541,18 @@ Global
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Release|Any CPU.Build.0 = Release|Any CPU
{0C3B9DEA-FC31-4039-ABB1-A087BB452B49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C3B9DEA-FC31-4039-ABB1-A087BB452B49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C3B9DEA-FC31-4039-ABB1-A087BB452B49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C3B9DEA-FC31-4039-ABB1-A087BB452B49}.Release|Any CPU.Build.0 = Release|Any CPU
{D4B74841-E40B-4856-92B4-BD98FB083DD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4B74841-E40B-4856-92B4-BD98FB083DD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4B74841-E40B-4856-92B4-BD98FB083DD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4B74841-E40B-4856-92B4-BD98FB083DD5}.Release|Any CPU.Build.0 = Release|Any CPU
{21B57D41-646E-43E3-B146-15F7CDE92A5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21B57D41-646E-43E3-B146-15F7CDE92A5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21B57D41-646E-43E3-B146-15F7CDE92A5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21B57D41-646E-43E3-B146-15F7CDE92A5E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -626,6 +644,9 @@ Global
{83033E58-88EB-4746-A15D-02E3DC1BB03A} = {1E6B9E8D-D5C1-4AD7-89F9-7179FEFD7C01}
{3B7382DA-99D4-48AA-B8B9-16D905D60BFF} = {1E6B9E8D-D5C1-4AD7-89F9-7179FEFD7C01}
{168BE615-DA0F-42FA-98F4-6694034DBF8A} = {1E6B9E8D-D5C1-4AD7-89F9-7179FEFD7C01}
{0C3B9DEA-FC31-4039-ABB1-A087BB452B49} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{D4B74841-E40B-4856-92B4-BD98FB083DD5} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{21B57D41-646E-43E3-B146-15F7CDE92A5E} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5A1CB38E-F77D-4A40-B3A9-9A70C3F3BC6D}
Expand Down
2 changes: 2 additions & 0 deletions doc/WebSite/Zero/Identity-Server-vNext.md
@@ -1,5 +1,7 @@
### Introduction

**Identity Server** changed its license model, so we suggest using [OpenIddict](OpenIddict.md) for new projects.

[Identity Server](http://identityserver.io/) is an open source **OpenID
Connect** and **OAuth 2.0** framework. It can be used to make your
application an **authentication / single sign on server**. It can also
Expand Down
131 changes: 131 additions & 0 deletions doc/WebSite/Zero/OpenIddict.md
@@ -0,0 +1,131 @@
### Introduction

[OpenIddict](https://github.com/openiddict/openiddict-core) aims at providing a versatile solution to implement OpenID Connect client, server and token validation support in any ASP.NET Core 2.1 (and higher) application. ASP.NET 4.6.1 (and higher) applications are also fully supported thanks to a native Microsoft.Owin 4.2 integration.

#### Startup Project

This document assumes that you have already created an ASP.NET Core based
project (including Module Zero) from the [startup templates](/Templates) and
have set it up to work. We created an [ASP.NET Core MVC startup
project](/Pages/Documents/Zero/Startup-Template-Core) for this demonstration.

### Installation

There are 3 NuGet packages:

- [**Abp.ZeroCore.OpenIddict**](https://www.nuget.org/packages/Abp.ZeroCore.OpenIddict)
is the main integration package.
- [**Abp.ZeroCore.OpenIddict.EntityFrameworkCore**](https://www.nuget.org/packages/Abp.ZeroCore.OpenIddict.EntityFrameworkCore)
is the storage provider for EF Core.
- [**Abp.AspNetCore.OpenIddict**](https://www.nuget.org/packages/Abp.AspNetCore.OpenIddict)
is the package for ASP.NET Core.

Install **Abp.ZeroCore.OpenIddict** package to your Core project and add a module dependency to `AbpZeroCoreOpenIddictModule`.

Install **Abp.AspNetCore.OpenIddict** package to your Web project and add a module dependency to `AbpZeroCoreOpenIddictEntityFrameworkCoreModule`.

Install **Abp.ZeroCore.OpenIddict.EntityFrameworkCore** package to your EntityFrameworkCore project and add a module dependency to `AbpAspNetCoreOpenIddictModule`.

### Configuration

Configuring and using OpenIddict with Abp.ZeroCore is similar to
independently using OpenIddict. You should read its [own
documentation](https://documentation.openiddict.com/index.html) to better understand how

#### EntityFrameworkCore Project

You need to implement `IOpenIddictDbContext` in your DbContext class. This will add Entities required by OpenIddict to your DbContext.

After this, call `modelBuilder.ConfigureOpenIddict();` in the `OnModelCreating` method of your DbContext.

Then, create repositories inherited from below classes;

* `EfCoreOpenIddictApplicationRepository`
* `EfCoreOpenIddictAuthorizationRepository`
* `EfCoreOpenIddictScopeRepository`
* `EfCoreOpenIddictTokenRepository`

#### Web Project

In your web project, create Controllers inherited from generic controllers defined in ASP.NET Boilerplate;

* `AuthorizeController`
* `TokenController`
* `UserInfoController`

Create a static class to Configure `OpenIddict` in your Web project and call it in Startup.cs;

```csharp
public static class OpenIddictRegistrar
{
public static void Register(
IServiceCollection services,
IConfigurationRoot configuration,
Action<OpenIddictCoreOptions> setupOptions)
{
services.Configure<AbpOpenIddictClaimsPrincipalOptions>(options =>
{
options.ClaimsPrincipalHandlers.Add<AbpDefaultOpenIddictClaimsPrincipalHandler>();
});

services.AddOpenIddict()

// Register the OpenIddict core components.
.AddCore(builder =>
{
builder
.SetDefaultApplicationEntity<OpenIddictApplicationModel>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorizationModel>()
.SetDefaultScopeEntity<OpenIddictScopeModel>()
.SetDefaultTokenEntity<OpenIddictTokenModel>();

builder
.AddApplicationStore<AbpOpenIddictApplicationStore>()
.AddAuthorizationStore<AbpOpenIddictAuthorizationStore>()
.AddScopeStore<AbpOpenIddictScopeStore>()
.AddTokenStore<AbpOpenIddictTokenStore>();
})

// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the token endpoint.
options.SetAuthorizationEndpointUris("connect/authorize", "connect/authorize/callback")
.SetTokenEndpointUris("connect/token")
.SetUserinfoEndpointUris("connect/userinfo");

// Enable the client credentials flow.
options.AllowClientCredentialsFlow();
options.AllowPasswordFlow();
options.AllowAuthorizationCodeFlow();

// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();

// Register the ASP.NET Core host and configure the ASP.NET Core options.
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableVerificationEndpointPassthrough()
.EnableStatusCodePagesIntegration();

options.DisableAccessTokenEncryption();
})

// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();

// Register the ASP.NET Core host.
options.UseAspNetCore();
});
}
}
```

You also need to fill data into OpenIddict tables by following its own [documentation](https://documentation.openiddict.com/)
5 changes: 4 additions & 1 deletion nupkg/pack.ps1
Expand Up @@ -54,7 +54,10 @@ $projects = (
"Abp.ZeroCore.EntityFrameworkCore",
"Abp.ZeroCore.IdentityServer4.vNext",
"Abp.ZeroCore.IdentityServer4.vNext.EntityFrameworkCore",
"Abp.ZeroCore.NHibernate"
"Abp.ZeroCore.NHibernate",
"Abp.ZeroCore.OpenIddict",
"Abp.ZeroCore.OpenIddict.EntityFrameworkCore",
"Abp.AspNetCore.OpenIddict"
)

# Rebuild solution
Expand Down
31 changes: 31 additions & 0 deletions src/Abp.AspNetCore.OpenIddict/Abp.AspNetCore.OpenIddict.csproj
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\common.props" />
<Import Project="..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>Abp.AspNetCore.OpenIddict</AssemblyName>
<PackageId>Abp.AspNetCore.OpenIddict</PackageId>
<PackageTags>asp.net;asp.net mvc;boilerplate;application framework;web framework;framework;domain driven design;asp.net core;openiddict</PackageTags>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<RootNamespace>Abp</RootNamespace>
<Description>Abp.AspNetCore.OpenIddict</Description>
<IsPackable>true</IsPackable>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Abp.ZeroCore\Abp.ZeroCore.csproj" />
<ProjectReference Include="..\Abp.ZeroCore.OpenIddict\Abp.ZeroCore.OpenIddict.csproj" />
<ProjectReference Include="..\Abp.AspNetCore\Abp.AspNetCore.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenIddict.AspNetCore" Version="5.1.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,15 @@
using System.Reflection;
using Abp.Modules;
using Abp.OpenIddict;

namespace Abp.AspNetCore.OpenIddict
{
[DependsOn(typeof(AbpAspNetCoreModule), typeof(AbpZeroCoreOpenIddictModule))]
public class AbpAspNetCoreOpenIddictModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Abp.AspNetCore.OpenIddict
{
public static class AbpAsyncEnumerableExtensions
{
public static Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

return ExecuteAsync();

async Task<List<T>> ExecuteAsync()
{
var list = new List<T>();

await foreach (var element in source)
{
list.Add(element);
}

return list;
}
}
}

}
@@ -0,0 +1,110 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Abp.Dependency;
using Abp.Runtime.Security;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using OpenIddict.Abstractions;

namespace Abp.AspNetCore.OpenIddict.Claims
{
public class AbpDefaultOpenIddictClaimsPrincipalHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
{
public virtual Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
{
var securityStampClaimType = context
.ScopeServiceProvider
.GetRequiredService<IOptions<IdentityOptions>>().Value
.ClaimsIdentity.SecurityStampClaimType;

AddSubClaim(context);

foreach (var claim in context.Principal.Claims)
{
if (claim.Type == AbpClaimTypes.TenantId)
{
claim.SetDestinations(
OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken
);
continue;
}

switch (claim.Type)
{
case OpenIddictConstants.Claims.PreferredUsername:
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
if (context.Principal.HasScope(OpenIddictConstants.Scopes.Profile))
{
claim.SetDestinations(
OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken
);
}

break;

case JwtRegisteredClaimNames.UniqueName:
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
if (context.Principal.HasScope(OpenIddictConstants.Scopes.Profile))
{
claim.SetDestinations(
OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken
);
}

break;

case OpenIddictConstants.Claims.Email:
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
if (context.Principal.HasScope(OpenIddictConstants.Scopes.Email))
{
claim.SetDestinations(
OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken
);
}

break;

case OpenIddictConstants.Claims.Role:
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
if (context.Principal.HasScope(OpenIddictConstants.Scopes.Roles))
{
claim.SetDestinations(
OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken
);
}

break;

default:
// Never include the security stamp in the access and identity tokens, as it's a secret value.
if (claim.Type != securityStampClaimType)
{
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
}

break;
}
}

return Task.CompletedTask;
}

protected virtual void AddSubClaim(AbpOpenIddictClaimsPrincipalHandlerContext context)
{
var nameIdClaim = context.Principal.Claims.First(c => c.Type == ClaimTypes.NameIdentifier);

if (context.Principal.Claims.All(c => c.Type != JwtRegisteredClaimNames.Sub))
{
context.Principal.AddClaim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value);
}
}
}
}

0 comments on commit 8658bcf

Please sign in to comment.