421.4 Abp microserices and Keycloak - chempkovsky/CS82ANGULAR GitHub Wiki

Notes

  • Only security aspects will be discussed and implemented.
  • In this article we will launch apps

Tools

Modify Web Host

  • open tstappWebHostModule.cs-file of the rupbes.tstapp.Web.Host.csproj-project
  • right before context.Services.AddAuthentication(options =>...-line add the code as below:
Click to show the code
        var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            configuration["AuthServer:MetaAddress"],
            new OpenIdConnectConfigurationRetriever(),
            new HttpDocumentRetriever());
        var discoveryDocument = Task.Run(async () => await configurationManager.GetConfigurationAsync()).Result;
        var signingKeys = discoveryDocument.SigningKeys;
  • modify .AddAbpOpenIdConnect("oidc", options => ...)-method call as follows:
Click to show the code
            .AddAbpOpenIdConnect("oidc", options =>
             {
                 options.Authority = configuration["AuthServer:Authority"];
                 options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
                 options.ResponseType = OpenIdConnectResponseType.Code;

                 options.ClientId = configuration["AuthServer:ClientId"];
                 options.ClientSecret = configuration["AuthServer:ClientSecret"];   // added

                 // BEGIN =="OR"==
                 // OR IssuerSigningKeys
                 options.TokenValidationParameters.IssuerSigningKeys = signingKeys;
                 // OR MetadataAddress
                 // options.MetadataAddress = configuration["AuthServer:MetaAddress"]; // added
                 // END =="OR"==
                 options.TokenValidationParameters.ValidateIssuer = true;
                 options.TokenValidationParameters.ValidateIssuerSigningKey = true;

                 options.SaveTokens = true;
                 options.GetClaimsFromUserInfoEndpoint = true;

                 options.Scope.Add("roles"); // for roles
                 options.Scope.Add("email");
                 options.Scope.Add("phone");
                 options.Scope.Add("tstapihost_scope"); // for audience
                 options.Scope.Add("tstauth_scope"); // for audience
                 options.Scope.Add("tenantid"); // for tenantid
                 options.Scope.Add("tstwebapp_scope"); // do not required



                 /* START: temporarily lines added  */
                 /*        for getting debug into about returned access_token and AuthenticationFailed */
                 /*        In production this lines must be deleted */
                 options.Events ??= new OpenIdConnectEvents();
                 options.Events.OnAuthenticationFailed = (context) =>
                 {
                     return Task.CompletedTask;
                 };
                 options.Events.OnTokenValidated = (context) =>
                 {
                     return Task.CompletedTask;
                 };
                 /* END: temporarily lines added  */
                 // options.ClaimActions.MapUniqueJsonKey()
             });

Create databases

  • start cmd.exe and run the commands
d:
cd D:\Development\rupbes.tstapp\host\rupbes.tstapp.Auth.Host
dotnet ef migrations add Initial
dotnet ef database update
cd D:\Development\rupbes.tstapp\host\rupbes.tstapp.HttpApi.Host
dotnet ef migrations add Initial
dotnet ef database update

Launch apps

  • run rupbes.tstapp.Auth.Host-app. It will create tstadmin-user
  • run rupbes.tstapp.Web.Host-app (do not stop rupbes.tstapp.Auth.Host-app)
    • login as tstadmin-user and password that was defined in keycloak (you can redefine password in keycloak for tstadmin-user. It must be different from 1q2w3E* for correct testing)
    • make sure the Administration menu is available (Users, Roles, Settings, Tenants, Permissions)
    • In rupbes.tstapp.Web.Host-app add tenant with a name tenant001
    • In Keycloak create tstadmin001 user with tenantid=tenant001 and repeat all the steps as for tstadmin-user
      • Add Admin-role for tstadmin001 user
      • Goto application database and copy ID of the tenant001
      • modify the code
               await seeder.SeedAsync(
                new DataSeedContext()
                .WithProperty("AdminId", new Guid("00000000-0000-0000-0000-000000000000")) // replace with Keycloak ID of `tstadmin001` user
                .WithProperty("AdminEmail", "[email protected]")
                .WithProperty("AdminUserName", "tstadmin001")
                // .WithProperty("AdminPassword", "1q2w3E*")
                );
  • do not forget to temporarily change the method
        public Task SeedAsync(DataSeedContext context)
        {
            return IdentityDataSeeder.SeedAsync(
                context?[AdminEmailPropertyName] as string ?? AdminEmailDefaultValue,
                context?[AdminPasswordPropertyName] as string ?? AdminPasswordDefaultValue,
                context?[AdminIdPropertyName] as Guid?,
                new Guid("00000000-0000-0000-0000-000000000000") // replace with ID of the `tenant001` // context?.TenantId,
                context?[AdminUserNamePropertyName] as string ?? AdminUserNameDefaultValue
            );
        }
  • After logging in as user tstadmin001 you will see that the Administration menu has changed a little

HttpApi Host

  • for the rupbes.tstapp.HttpApi.Host-project we have to implement ClaimsTransformation and TenantResolver

Add User Page

  • If you plane to use only RBAC (Role-based access control) you do not need to register any users in the APB application. To start the application, it is enough to create the Admin-role (without a user) in the CustomIdentityDataSeeder class.
Click to show the code
        [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;
            }
        }
  • But if you plan to assign permissions directly to the user you need to register users in Abp application.
    • Add User-page of the the application should be rewritten so that the user ID is editable
    • Add New Tenant-page of the the application should be rewritten so that the ID of admin user is editable
⚠️ **GitHub.com Fallback** ⚠️