419.2 Security and microservices of ABP framework applications - chempkovsky/CS82ANGULAR GitHub Wiki
The Abp developers have stopped providing a way to generate a solution with the "--separate-auth-server" flag for Community Edition. So, following their policy, we cannot explain how to configure the resource server for use with Abp's standalone authentication server. The only thing we can say is that it is still possible for Abp version 9.2.1. Let's assume you know how to do it.
- Create a folder for both solutions, run cmd.exe and in the command line terminal make this folder active.
- In the command line terminal run two commands
abp new company.abpauth --template app --ui-framework angular --theme leptonx-lite --skip-migrator --create-solution-folder --dont-run-install-libs --no-tests
abp new company.abpapp --template app --ui-framework angular --theme leptonx-lite --skip-migrations --skip-migrator --create-solution-folder --dont-run-install-libs --no-tests
-
company.abpauth
will play the role of authentication server- we use
--skip-migrator
-flag since we are goig to replaceIIdentityDataSeeder
-implementation
- we use
-
company.abpapp
will play the role of resource server- we use
--skip-migrations
-flag (and--skip-migrator
-flag) because we are going to delete some entities from DBContext
- we use
- Open
company.abpapp
-solution withAbp studio
- right click 'imports/Volo.Abp.OpenIddict' and choose
Remove
-menu item - right click 'imports/Volo.Abp.OpenIddict' and choose
Uninstall
-menu item
- right click 'imports/Volo.Abp.OpenIddict' and choose
- Open
company.abpapp
-solution withVisual Studio
- Open
company.abpapp.Domain.csproj
-file and remove (or comment) the line
<!--
<PackageReference Include="Volo.Abp.OpenIddict.Domain" Version="X.Y.Z" />
-->
- Open
company.abpapp.Domain\abpappDomainModule.cs
-file and remove (or comment) the lines:
// using Volo.Abp.OpenIddict; // -- delete this line
...
[DependsOn(
// typeof(AbpOpenIddictDomainModule), // -- delete this line
)]
- Open
company.abpapp.Domain.Shared.csproj
-file and remove (or comment) the line:
<!--
<PackageReference Include="Volo.Abp.OpenIddict.Domain.Shared" Version="X.Y.Z" />
-->
- Open
company.abpapp.Domain.Shared\abpappDomainSharedModule.cs
-file and remove (or comment) the lines:
// using Volo.Abp.OpenIddict; // -- delete this line
...
[DependsOn(
// typeof(AbpOpenIddictDomainSharedModule), // -- delete this line
)]
- Open
company.abpapp.EntityFrameworkCore.csproj
-file and remove (or comment) the line:
<!--
<PackageReference Include="Volo.Abp.OpenIddict.EntityFrameworkCore" Version="X.Y.Z" />
-->
- Open
company.abpapp.EntityFrameworkCore/abpappEntityFrameworkCoreModule.cs
-file and remove (or comment) the lines:
// using Volo.Abp.OpenIddict.EntityFrameworkCore; // -- delete this line
...
[DependsOn(
// typeof(AbpOpenIddictEntityFrameworkCoreModule), // -- delete this line
)]
- Open
company.abpapp.HttpApi.Host.csproj
-file and remove (or comment) the line:
<!--
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="X.Y.Z" />
-->
- Open
company.abpapp.HttpApi.Host.csproj
-file and remove (or comment) the line:
// using Microsoft.AspNetCore.Extensions.DependencyInjection; // -- delete this line
// using OpenIddict.Validation.AspNetCore; // -- delete this line
// using OpenIddict.Server.AspNetCore; // -- delete this line
// using Volo.Abp.Account.Web; // -- delete this line
// using Volo.Abp.OpenIddict; // -- delete this line
...
[DependsOn(
// typeof(AbpAccountWebOpenIddictModule), // -- delete this line
)]
- Open
company.abpapp-solution
withVisual Studio
- delete (or comment all the code) the
company.abpapp.Domain/OpenIddict/OpenIddictDataSeedContributor.cs
-file
- Modify the
company.abpapp.HttpApi.Host\abpappHttpApiHostModule.cs
-file:- remove (or comment all the code) the
PreConfigureServices
-method
- remove (or comment all the code) the
Click to show the code
/*
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
PreConfigure<OpenIddictBuilder>(builder =>
{
builder.AddValidation(options =>
{
options.AddAudiences("abpapp");
options.UseLocalServer();
options.UseAspNetCore();
});
});
if (!hostingEnvironment.IsDevelopment())
{
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
options.AddDevelopmentEncryptionAndSigningCertificate = false;
});
PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{
serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
});
}
}
*/
- Modify the
company.abpapp.HttpApi.Host\abpappHttpApiHostModule.cs
-file:- in the
ConfigureServices
-method remove (or comment all the code) as shown below:
- in the
Click to show the code
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (!configuration.GetValue<bool>("App:DisablePII"))
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
Microsoft.IdentityModel.Logging.IdentityModelEventSource.LogCompleteSecurityArtifact = true;
}
if (!configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"))
{
/*
Configure<OpenIddictServerAspNetCoreOptions>(options =>
{
options.DisableTransportSecurityRequirement = true;
});
*/
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddAbpJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
options.Audience = "abpapp";
});
Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
});
}
ConfigureAuthentication(context);
ConfigureUrls(configuration);
ConfigureBundles();
ConfigureConventionalControllers();
ConfigureHealthChecks(context);
ConfigureSwagger(context, configuration);
ConfigureVirtualFileSystem(context);
ConfigureCors(context, configuration);
}
- Modify the
company.abpapp.HttpApi.Host\abpappHttpApiHostModule.cs
-file:- in the
ConfigureAuthentication
-method remove (or comment all the code) as shown below:
- in the
Click to show the code
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
/*
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
*/
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true;
});
}
-
Modify the
company.abpapp.HttpApi.Host\abpappHttpApiHostModule.cs
-file:- in the
OnApplicationInitialization
-method remove (or comment) the line:// app.UseAbpOpenIddictValidation();
- in the
-
delete
Pages/Index.cshtml
andPages/Index.cshtml.cs
files -
create
Controllers
-folder -
create a class
Controllers/HomeController
-with the code
Click to show the code
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace company.abpapp.Controllers
{
public class HomeController : AbpController
{
public ActionResult Index()
{
return Redirect("~/swagger");
}
}
}
- add reference onto two packages in the
company.abpauth.HttpApi.Host.csproj
-file
Click to show the code
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="X.Y.Z" />
- open the
company.abpapp.EntityFrameworkCore/EntityFrameworkCore/abpappDbContext.cs
-file and remove two lines:-
// using Volo.Abp.OpenIddict.EntityFrameworkCore;
at the beginning of the file -
// builder.ConfigureOpenIddict();
in theOnModelCreating(ModelBuilder builder) {...}
-method
-
- Open
company.abpapp
-solution withVisual Studio
- build
company.abpapp.HttpApi.Host
-project to make sure everything is okay
- build
- Open
company.abpapp
-solution withVisual Studio
- add a
company.abpapp\company.abpapp.Domain\Identity\IdentityDataSeederEx
-class with a code
- add a
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;
namespace company.abpapp.Identity
{
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityDataSeeder))]
public class IdentityDataSeederEx : ITransientDependency, IIdentityDataSeeder
{
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 IdentityDataSeederEx(
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(
string adminEmail,
string adminPassword,
Guid? tenantId = null,
string? adminUserName = null)
{
Guid roleGuid = GuidGenerator.Create();
Guid adminGuid = new Guid("9ca3c973-3bb5-3ade-a137-3a1abeee2030");
if (tenantId != null)
{
adminGuid = GuidGenerator.Create();
}
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;
}
adminUser = await UserRepository.FindAsync(adminGuid);
if (adminUser != null)
{
return result;
}
adminUser = new IdentityUser(
adminGuid,
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 = await RoleRepository.FindAsync(roleGuid);
}
if (adminRole == null)
{
adminRole = new IdentityRole(
roleGuid,
adminRoleName,
tenantId
)
{
IsStatic = true,
IsPublic = true
};
(await RoleManager.CreateAsync(adminRole)).CheckErrors();
result.CreatedAdminRole = true;
}
(await UserManager.AddToRoleAsync(adminUser, adminRoleName)).CheckErrors();
return result;
}
}
}
}
- Open
company.abpauth
-solution withVisual Studio
- add a
company.abpauth\company.abpauth.Domain\Identity\IdentityDataSeederEx
-class with the same code as above (namespace will be different).
- add a
-
IdentityDataSeeder
is called in two places- by DbMigrator
- when a new tenant is created
-
admin
-user is created with predefined RowId. As we mentioned in the previous article Abp uses RowId (not a userName or email) at authorization time. So to login into resource server very first time we need the same RowId in both databases. This is not a code for production. This is just a hint of what to think about.
- Open
company.abpauth
-solution withVisual Studio
- modify
company.abpauth/company.abpauth.DbMigrator/appsettings.json
-file - add three new items into
OpenIddict/Applications
list
- modify
Click to show the code
"abpapp_Client": {
"ClientId": "abpapp_Client",
"RootUrl": "http://localhost:4201"
},
"abpapp_App": {
"ClientId": "abpapp_App",
"RootUrl": "https://localhost:44346/"
},
"abpapp_Swagger": {
"ClientId": "abpapp_Swagger",
"RootUrl": "https://localhost:44346/"
}
- We copied two items from the
company.abpapp/company.abpapp.DbMigrator/appsettings.json
-file. In your case the ports will be different except 4201.- we are going to run angular app of the 'abpapp' using port = 4201
- We will not register
"abpapp_App": {"ClientId": "abpapp_App", "RootUrl": "https://localhost:44346/"}
-record untill introspection. This record represents resource service
- Open
company.abpapp
-solution withVisual Studio
- modify
company.abpapp/company.abpapp.HttpApi.Host/appsettings.json
-file-
AuthServer/Authority
= "https://localhost:44388". It is a port of thecompany.abpauth
-server. -
App/CorsOrigins
= "https://*.abpapp.com,http://localhost:4201". We are going to run angular app of the 'abpapp' using port = 4201
-
- modify
- Open
company.abpapp
-solution withVisual Studio
- modify
company.abpauth/company.abpauth.HttpApi.Host/appsettings.json
-file-
App/CorsOrigins
= "https://*.abpauth.com,http://localhost:4200,http://localhost:4201"
-
- modify
- Open
company.abpapp
-solution withVisual Studio
- modify
company.abpauth/company.abpauth.Domain/OpenIddict/OpenIddictDataSeedContributor.cs
-file- add scope registration in the
CreateScopesAsync()
-method
- add scope registration in the
- modify
Click to show the code
private async Task CreateScopesAsync()
{
if (await _openIddictScopeRepository.FindByNameAsync("abpauth") == null)
{
await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor {
Name = "abpauth", DisplayName = "abpauth API", Resources = { "abpauth" }
});
await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor
{
Name = "abpapp", DisplayName = "abpapp API", Resources = { "abpapp" }
});
}
}
- add two apps registration in the
CreateApplicationsAsync()
-method. Insert the code at the end of the method
Click to show the code
commonScopes = new List<string> {
OpenIddictConstants.Permissions.Scopes.Address,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Phone,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles,
"abpapp"
};
// Swagger Client
swaggerClientId = configurationSection["abpapp_Swagger:ClientId"];
if (!swaggerClientId.IsNullOrWhiteSpace())
{
var swaggerRootUrl = configurationSection["abpapp_Swagger:RootUrl"]?.TrimEnd('/');
await CreateApplicationAsync(
applicationType: OpenIddictConstants.ApplicationTypes.Web,
name: swaggerClientId!,
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Abpapp Swagger Application",
secret: null,
grantTypes: new List<string> { OpenIddictConstants.GrantTypes.AuthorizationCode, },
scopes: commonScopes,
redirectUris: new List<string> { $"{swaggerRootUrl}/swagger/oauth2-redirect.html" },
clientUri: swaggerRootUrl.EnsureEndsWith('/') + "swagger",
logoUri: "/images/clients/swagger.svg"
);
}
//Console Test / Angular Client
consoleAndAngularClientId = configurationSection["abpapp_Client:ClientId"];
if (!consoleAndAngularClientId.IsNullOrWhiteSpace())
{
var consoleAndAngularClientRootUrl = configurationSection["abpapp_Client:RootUrl"]?.TrimEnd('/');
await CreateApplicationAsync(
applicationType: OpenIddictConstants.ApplicationTypes.Web,
name: consoleAndAngularClientId!,
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Abpapp Angular Application",
secret: null,
grantTypes: new List<string> {
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.Password,
OpenIddictConstants.GrantTypes.ClientCredentials,
OpenIddictConstants.GrantTypes.RefreshToken,
"LinkLogin",
"Impersonation"
},
scopes: commonScopes,
redirectUris: new List<string> { consoleAndAngularClientRootUrl },
postLogoutRedirectUris: new List<string> { consoleAndAngularClientRootUrl },
clientUri: consoleAndAngularClientRootUrl,
logoUri: "/images/clients/angular.svg"
);
}
- build and run
company.abpauth.DbMigrator
- build and run
company.abpapp.DbMigrator
- run
abp install-libs
for each solution
- modify
environment.ts
-file of the angular project ofabpapp
-solution-
baseUrl
= "http://localhost:4201" -
oAuthConfig/issuer
= "https://localhost:44388/" -
oAuthConfig/clientId
= "abpapp_Client"
-
Click to show the code
import { Environment } from '@abp/ng.core';
const baseUrl = 'http://localhost:4201';
const oAuthConfig = {
issuer: 'https://localhost:44388/',
redirectUri: baseUrl,
clientId: 'abpapp_Client',
responseType: 'code',
scope: 'offline_access abpapp',
requireHttps: true,
};
export const environment = {
production: false,
application: {
baseUrl,
name: 'abpapp',
},
oAuthConfig,
apis: {
default: {
url: 'https://localhost:44346',
rootNamespace: 'company.abpapp',
},
AbpAccountPublic: {
url: oAuthConfig.issuer,
rootNamespace: 'AbpAccountPublic',
},
},
} as Environment;
- build and run
company.abpauth.HttpApi.Host
- build and run
company.abpapp.HttpApi.Host
- run
ng serve -o --port 4201
-command for angular project ofabpapp
-solution - run
ng serve -o --port 4200
-command for angular project ofabpauth
-solution. It plays the role of the back-end application to register new users.