421.2 Abp microserices and Keycloak - chempkovsky/CS82ANGULAR GitHub Wiki

Notes

  • Only security aspects will be discussed and implemented.
  • In this article we will create an analogue of the rupbes.tstapp.AuthServer-project
    • It will not work as Authorization server for the front app (rupbes.tstapp.Web.Host)
    • But it will not work as Authentication server (Keycloak will be used as Authentication server)

Tools

Create Initial project

  • Start ABP Studio

  • Open rupbes.tstapp-solution with ABP Studio

  • right click Host-folder

    • select Add/Package/New Package
    • In the dialog select Http Api Host application
    • In the dialog enter package name = rupbes.tstapp.Auth.Host
    • Open rupbes.tstapp.Auth.Host.csproj-file and replace <RootNamespace>rupbes.tstapp.Auth.Host</RootNamespace> with <RootNamespace>rupbes.tstapp</RootNamespace>
    • In the files Program.cs, HostHttpApiHostModule.cs and Controllers/HomeController.cs modify the namespace, i.e. replace rupbes.tstapp.Auth.Host with rupbes.tstapp.
    • build the project but do not run it
  • here is rupbes.tstapp.Auth.Host.csproj-file

Click to show the code
<Project Sdk="Microsoft.NET.Sdk.Web">

    <Import Project="..\..\common.props" />

    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <Nullable>enable</Nullable>
        <RootNamespace>rupbes.tstapp</RootNamespace>
        <GenerateDocumentationFile>true</GenerateDocumentationFile>
        <CopyLocalLockFileAssemblies>True</CopyLocalLockFileAssemblies>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Serilog.AspNetCore" Version="4.0.0" />
        <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="9.2.3" />
        <PackageReference Include="Volo.Abp.Autofac" Version="9.2.3" />
        <PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="9.2.3" />
        <PackageReference Include="Volo.Abp.Swashbuckle" Version="9.2.3" />
    </ItemGroup>

  <ItemGroup>
    <Compile Remove="Logs\**" />
    <Content Remove="Logs\**" />
    <EmbeddedResource Remove="Logs\**" />
    <None Remove="Logs\**" />
  </ItemGroup>

</Project>

EntityFrameworkCore

  • In the rupbes.tstapp.Auth.Host.csproj-project add EntityFrameworkCore-folder
  • create EntityFrameworkCore/tstappAuthHostMigrationsDbContext.cs-class (we name the class the same way as it is done for the tstappHttpApiHostMigrationsDbContext.cs class of rupbes.tstapp.HttpApi.Host.csproj-project)
    • the code of the class is as follows:
Click to show the code
using Microsoft.EntityFrameworkCore;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.TenantManagement.EntityFrameworkCore;

namespace rupbes.tstapp.EntityFrameworkCore
{
    public class tstappAuthHostMigrationsDbContext : AbpDbContext<tstappAuthHostMigrationsDbContext>
    {
        public tstappAuthHostMigrationsDbContext(DbContextOptions<tstappAuthHostMigrationsDbContext> options)
            : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.ConfigurePermissionManagement();
            modelBuilder.ConfigureSettingManagement();
            modelBuilder.ConfigureAuditLogging();
            modelBuilder.ConfigureIdentity();
            modelBuilder.ConfigureFeatureManagement();
            modelBuilder.ConfigureTenantManagement();
        }
    }
}
  • it requires modification of the rupbes.tstapp.Auth.Host.csproj-file. All new package-refs we add in the separete <ItemGroup>:
Click to show the code
	<ItemGroup>
                <PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="9.2.3" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4">
			<PrivateAssets>all</PrivateAssets>
			<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
		</PackageReference>


		<PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.Identity.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.AuditLogging.EntityFrameworkCore" Version="9.2.3" />

	</ItemGroup>	
  • create EntityFrameworkCore/tstappAuthHostMigrationsDbContextFactory.cs-class (we name the class the same way as it is done for the tstappHttpApiHostMigrationsDbContextFactory.cs class of rupbes.tstapp.HttpApi.Host.csproj-project)
    • the code of the class is as follows:
Click to show the code
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;

namespace rupbes.tstapp.EntityFrameworkCore
{
    public class tstappAuthHostMigrationsDbContextFactory : IDesignTimeDbContextFactory<tstappAuthHostMigrationsDbContext>
    {
        public tstappAuthHostMigrationsDbContext CreateDbContext(string[] args)
        {
            var configuration = BuildConfiguration();

            var builder = new DbContextOptionsBuilder<tstappAuthHostMigrationsDbContext>()
                .UseSqlServer(configuration.GetConnectionString("Default"));

            return new tstappAuthHostMigrationsDbContext(builder.Options);
        }

        private static IConfigurationRoot BuildConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false);
            return builder.Build();
        }
    }
}
  • it does not require modification of the rupbes.tstapp.Auth.Host.csproj-file.
  • now we near to use ef migrations (left appsettings.json):
dotnet ef migrations add Initial
dotnet ef database update

Data

Click to show the code
using JetBrains.Annotations;
using System;
using System.Threading.Tasks;
using Volo.Abp.Identity;

namespace rupbes.tstapp.Data
{
    public interface ICustomIdentityDataSeeder
    {
        Task<IdentityDataSeedResult> SeedAsync(
            [NotNull] string adminEmail,
            [NotNull] string adminPassword,
            Guid? adminId = null,
            Guid? tenantId = null,
            string? adminUserName = null);
    }
}
  • Add Data/CustomIdentityDataSeeder.cs-class
Click to show the code
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using JetBrains.Annotations;
using IdentityRole = Volo.Abp.Identity.IdentityRole;
using IdentityUser = Volo.Abp.Identity.IdentityUser;


namespace rupbes.tstapp.Data
{
    public class CustomIdentityDataSeeder : ITransientDependency, ICustomIdentityDataSeeder
    {
        protected IGuidGenerator GuidGenerator { get; }
        protected IIdentityRoleRepository RoleRepository { get; }
        protected IIdentityUserRepository UserRepository { get; }
        protected ILookupNormalizer LookupNormalizer { get; }
        protected IdentityUserManager UserManager { get; }
        protected IdentityRoleManager RoleManager { get; }
        protected ICurrentTenant CurrentTenant { get; }
        protected IOptions<IdentityOptions> IdentityOptions { get; }

        public CustomIdentityDataSeeder(
            IGuidGenerator guidGenerator,
            IIdentityRoleRepository roleRepository,
            IIdentityUserRepository userRepository,
            ILookupNormalizer lookupNormalizer,
            IdentityUserManager userManager,
            IdentityRoleManager roleManager,
            ICurrentTenant currentTenant,
            IOptions<IdentityOptions> identityOptions)
        {
            GuidGenerator = guidGenerator;
            RoleRepository = roleRepository;
            UserRepository = userRepository;
            LookupNormalizer = lookupNormalizer;
            UserManager = userManager;
            RoleManager = roleManager;
            CurrentTenant = currentTenant;
            IdentityOptions = identityOptions;
        }

        [UnitOfWork]
        public virtual async Task<IdentityDataSeedResult> SeedAsync(
            [NotNull] string adminEmail,
            [NotNull] string adminPassword,
            Guid? adminId,
            Guid? tenantId = null,
            string? adminUserName = null)
        {
            Check.NotNullOrWhiteSpace(adminEmail, nameof(adminEmail));
            Check.NotNullOrWhiteSpace(adminPassword, nameof(adminPassword));

            using (CurrentTenant.Change(tenantId))
            {
                await IdentityOptions.SetAsync();

                var result = new IdentityDataSeedResult();
                //"admin" user
                if (adminUserName.IsNullOrWhiteSpace())
                {
                    adminUserName = IdentityDataSeedContributor.AdminUserNameDefaultValue;
                }
                var adminUser = await UserRepository.FindByNormalizedUserNameAsync(
                    LookupNormalizer.NormalizeName(adminUserName)
                );

                if (adminUser != null)
                {
                    return result;
                }
                var adminIdEx =
                              adminId.HasValue
                              ?
                              ((adminId.Value == Guid.Empty) ? GuidGenerator.Create() : adminId.Value)
                              :
                              GuidGenerator.Create();


                adminUser = new IdentityUser(
                    adminIdEx,
                    adminUserName,
                    adminEmail,
                    tenantId
                )
                {
                    Name = adminUserName
                };

                (await UserManager.CreateAsync(adminUser, adminPassword, validatePassword: false)).CheckErrors();
                result.CreatedAdminUser = true;

                //"admin" role
                const string adminRoleName = "admin";
                var adminRole =
                    await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.NormalizeName(adminRoleName));
                if (adminRole == null)
                {
                    adminRole = new IdentityRole(
                        GuidGenerator.Create(),
                        adminRoleName,
                        tenantId
                    )
                    {
                        IsStatic = true,
                        IsPublic = true
                    };

                    (await RoleManager.CreateAsync(adminRole)).CheckErrors();
                    result.CreatedAdminRole = true;
                }

                (await UserManager.AddToRoleAsync(adminUser, adminRoleName)).CheckErrors();

                return result;
            }
        }
    }
}

Add Data/CustomIdentityDataSeedContributor.cs-class

Click to show the code
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;

namespace rupbes.tstapp.Data
{
    // https://abp.io/support/questions/6448/Issues-Replacing-PermissionDataSeedContributor
    // https://github.com/abpframework/abp/blob/dev/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDataSeedContributor.cs
    // https://abp.io/docs/latest/framework/infrastructure/data-seeding#idataseedcontributor

    /*

        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            PostConfigure<AbpDataSeedOptions>(options =>
            {
                options.Contributors.RemoveAll(x => x == typeof(IdentityDataSeedContributor));
            });
        }

        public class YourIdentityDataSeedContributor : IDataSeedContributor, ITransientDependency 
        {
        }
    */


    // [Dependency(ReplaceServices = true)]
    // [ExposeServices(typeof(CustomIdentityDataSeedContributor), typeof(IdentityDataSeedContributor), typeof(IDataSeedContributor))]
    // public class CustomIdentityDataSeedContributor: IdentityDataSeedContributor

    public class CustomIdentityDataSeedContributor : IDataSeedContributor, ITransientDependency
    {
        public const string AdminIdPropertyName = "AdminId";
        public const string AdminEmailPropertyName = "AdminEmail";
        public const string AdminEmailDefaultValue = "[email protected]";
        public const string AdminUserNamePropertyName = "AdminUserName";
        public const string AdminUserNameDefaultValue = "admin";
        public const string AdminPasswordPropertyName = "AdminPassword";
        public const string AdminPasswordDefaultValue = "1q2w3E*";

        protected ICustomIdentityDataSeeder IdentityDataSeeder { get; }

        public CustomIdentityDataSeedContributor(ICustomIdentityDataSeeder identityDataSeeder)
        {
            IdentityDataSeeder = identityDataSeeder;
        }

        public Task SeedAsync(DataSeedContext context)
        {
            return IdentityDataSeeder.SeedAsync(
                context?[AdminEmailPropertyName] as string ?? AdminEmailDefaultValue,
                context?[AdminPasswordPropertyName] as string ?? AdminPasswordDefaultValue,
                context?[AdminIdPropertyName] as Guid?,
                context?.TenantId,
                context?[AdminUserNamePropertyName] as string ?? AdminUserNameDefaultValue
            );
        }
    }
}

ClaimsTransformation

  • We need the transformation from the keycloak roles notation into Abp role notation.
  • In the rupbes.tstapp.Auth.Host.csproj-project add ClmsTransform-folder
  • Add ClmsTransform/CustomClaimsTransformation.cs-class
    • "HOST" is a name of the host
    • other name is a tenant name
Click to show the code
using Microsoft.AspNetCore.Authentication;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace rupbes.tstapp.ClmsTransform
{
    public class CustomClaimsTransformation : IClaimsTransformation
    {
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            Claim? clm = principal.FindFirst(claim => claim.Type == "resource_access");
            if (clm != null)
            {
                string val = clm.Value;
                JsonNode? jsn01 = JsonObject.Parse(val);
                if ((jsn01 != null) && (jsn01.GetValueKind() == JsonValueKind.Object))
                {
                    HashSet<string> hs = new HashSet<string>();
                    JsonObject jso01 = jsn01.AsObject();
                    foreach (var kv in jso01)
                    {
                        string appName = kv.Key;
                        JsonNode? jsn02 = kv.Value;
                        if ((jsn02 != null) && (jsn02.GetValueKind() == JsonValueKind.Object))
                        {
                            JsonObject jso02 = jsn02.AsObject();
                            foreach (var kv2 in jso02)
                            {
                                if (kv2.Key == "roles")
                                {
                                    JsonNode? jsn03 = kv2.Value;
                                    if ((jsn03 != null) && (jsn03.GetValueKind() == JsonValueKind.Array))
                                    {
                                        JsonArray roles = jsn03.AsArray();
                                        foreach (var role in roles)
                                        {
                                            if (role != null)
                                            {
                                                if (role.GetValueKind() == JsonValueKind.String)
                                                {
                                                    string roleName = role.ToJsonString();
                                                    if (roleName != null)
                                                    {
                                                        hs.Add(roleName.Trim('"'));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (hs.Count > 0)
                    {
                        ClaimsIdentity claimsIdentity = new ClaimsIdentity();
                        foreach (var role in hs)
                        {
                            claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
                        }
                        principal.AddIdentity(claimsIdentity);
                    }
                }
            }
            return Task.FromResult(principal);
        }
    }
}

TenantResolver

  • we nedd to transform keycloak tenantid claim into TenantId of the current user
  • In the rupbes.tstapp.Auth.Host.csproj-project add TenantResolver-folder
  • Add TenantResolver/CustomTenantResolveContributor.cs-class
Click to show the code
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
using System.Linq;

namespace rupbes.tstapp.TenantResolver
{
    public class CustomTenantResolveContributor : CurrentUserTenantResolveContributor
    {
        public override Task ResolveAsync(ITenantResolveContext context)
        {
            // A tenant resolver should set "context.TenantIdOrName" if it can determine it.If not,
            // just leave it as is to allow the next resolver to determine it.
            // "context.ServiceProvider" can be used if you need to additional services to resolve
            // from the dependency injection system.

            var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
            if (currentUser.IsAuthenticated)
            {
                var accessor = context.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
                Claim? cl = accessor.Principal.Claims.FirstOrDefault(claim => claim.Type == "tenantid");
                if (cl != null)
                {
                    //Guid guid = Guid.NewGuid();
                    //string tnnt = guid.ToString();
                    string tnnt = cl.Value;
                    if (!(String.IsNullOrEmpty(tnnt) || "HOST".Equals(tnnt, StringComparison.OrdinalIgnoreCase)))
                    {
                        context.TenantIdOrName = cl.Value; // currentUser.TenantId?.ToString();
                    }
                    context.Handled = true;
                }
            }
            return Task.CompletedTask;
        }

    }
}

HostHttpApiHostModule

  • modify HostHttpApiHostModule.cs-file of rupbes.tstapp.Auth.Host.csproj-project as follows:
Click to show the code
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using rupbes.tstapp.ClmsTransform;
using System;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.Autofac;
using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.SqlServer;
using Volo.Abp.FeatureManagement;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.PermissionManagement.HttpApi;
using Volo.Abp.PermissionManagement.Identity;
using Volo.Abp.SettingManagement;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.Swashbuckle;
using Volo.Abp.TenantManagement;
using Volo.Abp.TenantManagement.EntityFrameworkCore;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Protocols;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using StackExchange.Redis;
using Volo.Abp.Data;
using rupbes.tstapp.TenantResolver;
using rupbes.tstapp.MultiTenancy;
using Microsoft.AspNetCore.DataProtection;

namespace rupbes.tstapp;

[DependsOn(

    typeof(AbpAspNetCoreMvcUiMultiTenancyModule),

    typeof(AbpAuditLoggingEntityFrameworkCoreModule),
    typeof(AbpCachingStackExchangeRedisModule),
    typeof(AbpEntityFrameworkCoreSqlServerModule),


    typeof(AbpIdentityEntityFrameworkCoreModule),
    typeof(AbpIdentityApplicationModule),
    typeof(AbpIdentityHttpApiModule),


    typeof(AbpPermissionManagementDomainIdentityModule), // required for Permission administration
    typeof(AbpPermissionManagementEntityFrameworkCoreModule),
    typeof(AbpPermissionManagementApplicationModule),
    typeof(AbpPermissionManagementHttpApiModule),


    typeof(AbpSettingManagementEntityFrameworkCoreModule),
    typeof(AbpSettingManagementApplicationModule),
    typeof(AbpSettingManagementHttpApiModule),


    typeof(AbpFeatureManagementEntityFrameworkCoreModule),
    typeof(AbpFeatureManagementApplicationModule),
    typeof(AbpFeatureManagementHttpApiModule),


    typeof(AbpTenantManagementEntityFrameworkCoreModule),
    typeof(AbpTenantManagementApplicationModule),
    typeof(AbpTenantManagementHttpApiModule),

    typeof(AbpAutofacModule),
    // typeof(AbpAspNetCoreMvcModule)
    typeof(AbpAspNetCoreSerilogModule),
    typeof(AbpSwashbuckleModule)
)]
public class HostHttpApiHostModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();

        Configure<AbpDbContextOptions>(options =>
        {
            options.UseSqlServer();
        });
        Configure<AbpMultiTenancyOptions>(options =>
        {
            options.IsEnabled = MultiTenancyConsts.IsEnabled;
        });
        context.Services.AddAbpSwaggerGenWithOAuth(
            configuration["AuthServer:Authority"]!,
            new Dictionary<string, string>
            {
                {"rappname", "rappname API"}
            },
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "rappname DefHttpApi API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
            });

        Configure<AbpLocalizationOptions>(options =>
        {
            options.Languages.Add(new LanguageInfo("ar", "ar", "العربية"));
            options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
            options.Languages.Add(new LanguageInfo("en", "en", "English"));
            options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)"));
            options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish"));
            options.Languages.Add(new LanguageInfo("fr", "fr", "Français"));
            options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi"));
            options.Languages.Add(new LanguageInfo("is", "is", "Icelandic"));
            options.Languages.Add(new LanguageInfo("it", "it", "Italiano"));
            options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar"));
            options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
            options.Languages.Add(new LanguageInfo("ro-RO", "ro-RO", "Română"));
            options.Languages.Add(new LanguageInfo("ru", "ru", "Русский"));
            options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak"));
            options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
            options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
            options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文"));
            options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch"));
            options.Languages.Add(new LanguageInfo("es", "es", "Español"));
            options.Languages.Add(new LanguageInfo("el", "el", "Ελληνικά"));
        });


        context.Services.AddTransient<IClaimsTransformation, CustomClaimsTransformation>();

        // add keycloak signing schemas
        var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            configuration["AuthServer:MetaAddress"],
            new OpenIdConnectConfigurationRetriever(),
            new HttpDocumentRetriever());
        var discoveryDocument = System.Threading.Tasks.Task.Run(async () => await configurationManager.GetConfigurationAsync()).Result;
        var signingKeys = discoveryDocument.SigningKeys;



        //
        // Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler
        // will be used for Authentication
        //
        context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddAbpJwtBearer(options =>
            {
                options.Authority = configuration["AuthServer:Authority"];
                options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
                options.Audience = configuration["AuthServer:Audience"];                                 // modified

                options.TokenValidationParameters.ValidateIssuer = true;                                 // added
                options.TokenValidationParameters.ValidateAudience = true;                               // added
                options.TokenValidationParameters.ValidateLifetime = true;                               // added
                options.TokenValidationParameters.ValidateTokenReplay = true;                            // added
                options.TokenValidationParameters.ValidIssuer = configuration["AuthServer:Issuer"];      // added
                // == OR ==
                options.TokenValidationParameters.IssuerSigningKeys = signingKeys;                       // added
                // == OR ==
                // options.MetadataAddress = configuration["AuthServer:MetaAddress"];                    // added

                /* START: lines added  */
                options.Events ??= new JwtBearerEvents();                                                // added
                options.Events.OnTokenValidated = cntxt =>      // temporarily for getting debug into about returned access_token
                {
                    if (cntxt.Principal?.Identity == null)
                        return Task.CompletedTask;
                    else
                        return Task.CompletedTask;
                };
                options.Events.OnMessageReceived = cntxt =>     // temporarily for getting debug into about if the client connects
                {
                    return Task.CompletedTask;
                };
                options.Events.OnAuthenticationFailed = cntxt => // temporarily for getting debug into about AuthenticationFailed
                {
                    return Task.CompletedTask;
                };
                /* END: lines added  */

                //options.ClaimActions.MapUniqueJsonKey
            });
        Configure<AbpDistributedCacheOptions>(options =>
        {
            options.KeyPrefix = "tstapp:";
        });

        var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("rappname");
        if (!hostingEnvironment.IsDevelopment())
        {
            var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
            dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "rappname-Protection-Keys");
        }

        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.RemovePostFix("/"))
                            .ToArray() ?? Array.Empty<string>()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });

        PostConfigure<AbpDataSeedOptions>(options =>
        {
            options.Contributors.RemoveAll(x => x == typeof(IdentityDataSeedContributor));
        });

        Configure<AbpTenantResolveOptions>(options =>
        {
            options.TenantResolvers.Clear();
            options.TenantResolvers.Add(new CustomTenantResolveContributor());
        });

    }


    public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseCorrelationId();
        app.MapAbpStaticAssets();
        app.UseRouting();
        app.UseCors();
        app.UseAuthentication();
        if (MultiTenancyConsts.IsEnabled)
        {
            app.UseMultiTenancy();
        }
        app.UseAbpRequestLocalization();
        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API");

            var configuration = context.GetConfiguration();
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
            options.OAuthScopes("rappname");
        });
        app.UseAuditing();
        app.UseAbpSerilogEnrichers();
        app.UseConfiguredEndpoints();



        await SeedData(context);
    }
    private async Task SeedData(ApplicationInitializationContext context)
    {
        using (var scope = context.ServiceProvider.CreateScope())
        {
            var seeder = scope.ServiceProvider
                .GetRequiredService<IDataSeeder>();
            await seeder.SeedAsync(
                new DataSeedContext()
                .WithProperty("AdminId", new Guid("0953d853-d063-41ab-8635-08b92ef81869"))
                .WithProperty("AdminEmail", "[email protected]")
                .WithProperty("AdminUserName", "testuser")
                // .WithProperty("AdminPassword", "1q2w3E*")
                );
        }
    }

}
  • it requires to change rupbes.tstapp.Auth.Host.csproj-file. All new package-refs we add in the separete :
Click to show the code
	<ItemGroup>
                <PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="9.2.3" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4">
			<PrivateAssets>all</PrivateAssets>
			<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
		</PackageReference>


		<PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.Identity.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.AuditLogging.EntityFrameworkCore" Version="9.2.3" />

		<PackageReference Include="Volo.Abp.PermissionManagement.Application" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.PermissionManagement.HttpApi" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.PermissionManagement.Domain.Identity" Version="9.2.3" />

		<PackageReference Include="Volo.Abp.FeatureManagement.Application" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.FeatureManagement.HttpApi" Version="9.2.3" />

		<PackageReference Include="Volo.Abp.TenantManagement.Application" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.TenantManagement.HttpApi" Version="9.2.3" />

		<PackageReference Include="Volo.Abp.SettingManagement.Application" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.SettingManagement.HttpApi" Version="9.2.3" />
		

		<PackageReference Include="Volo.Abp.Account.Application" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.Account.HttpApi" Version="9.2.3" />

		<PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic" Version="9.2.3" />
		<PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy" Version="9.2.3" />

		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
		<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="9.2.3" />

		<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.4" />
		<PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="9.2.3" />

		<ProjectReference Include="..\..\src\rupbes.tstapp.Application.Contracts\rupbes.tstapp.Application.Contracts.csproj" />
		<ProjectReference Include="..\rupbes.tstapp.Host.Shared\rupbes.tstapp.Host.Shared.csproj" />

	</ItemGroup>	

What is next

  • The next step is to configure Keycloak for new applications:
    • realm
    • user
    • apps
    • roles
    • user properties
⚠️ **GitHub.com Fallback** ⚠️