In addition to the configuration options from my initial post, it was necessary to add the following also:
context.Services.Configure<AbpAccountOptions>(options =>
{
//For impersonation in Saas module
options.TenantAdminUserName = "admin";
options.ImpersonationTenantPermission = SaasHostPermissions.Tenants.Impersonation;
//For impersonation in Identity module
options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
});
Your documentation should be changed to highlight the fact that the 5.0+ templates SUPPORT impersonation but that you must ENABLE it using the instructions in the document based on your template type. I was confused about it being "enabled" already as per the docs.
Yes, I did check the document. It says
Impersonation is enabled by defautl in ABP v5.0 and above.
I am using 6.0.2
ABP Framework version: v6.0.2
UI type: MVC
DB provider: EF Core
Tiered (MVC) or Identity Server Separated (Angular): no (single no-layers template with added modules)
Exception message and stack trace:
[15:02:43 DBG] The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessRequestContext was successfully processed by OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers+InferIssuerFromHost. [15:02:43 INF] The request address matched a server endpoint: Authorization. [15:02:43 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers+InferEndpointType. [15:02:43 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by Volo.Abp.Account.Web.Pages.Account.OpenIddictImpersonateInferEndpointType. [15:02:43 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was marked as skipped by Volo.Abp.Account.Web.Pages.Account.OpenIddictImpersonateInferEndpointType. [15:02:43 DBG] AuthenticationScheme: Identity.Application was successfully authenticated. [15:02:46 ERR] An unhandled exception has occurred while executing the request. Volo.Abp.BusinessException: Exception of type 'Volo.Abp.BusinessException' was thrown. at Volo.Abp.Account.Public.Web.Pages.Account.ImpersonateUserModel.OnPostAsync() at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject) at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments) at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync() at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync() at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context) at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync() at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResourceFilter() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Volo.Abp.AspNetCore.Serilog.AbpSerilogMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Volo.Abp.AspNetCore.Uow.AbpUnitOfWorkMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Volo.Abp.AspNetCore.MultiTenancy.MultiTenancyMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Builder.ApplicationBuilderAbpOpenIddictMiddlewareExtension.<>c__DisplayClass0_0.<<UseAbpOpenIddictValidation>b__0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Volo.Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Volo.Abp.AspNetCore.Tracing.AbpCorrelationIdMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Steps to reproduce the issue:"
(I created my no-layer project using 6.0.1 and then upgraded but not sure that's necessary to reproduce)
enable multitenancy, tenant impersonation and user impersonation
log in with admin user.
create another admin user.
impersonate the new user from the users list using the (Log in With This User) action option
//For impersonation in Saas module
context.Services.Configure<AbpSaasHostWebOptions>(options => { options.EnableTenantImpersonation = true; });
//For impersonation in Identity module
context.Services.Configure<AbpIdentityWebOptions>(options => { options.EnableUserImpersonation = true; });
Not sure if this issue is being experienced by others or what the problem here could be without pulling the Account Pro project and integrating it with my template in order to step through (is that even possible w/ no-layers template?)
So, what can be done to eliminate this issue?
It may be worthy of note that I did extend the user entity with a GUID property using the following Extensibility procedure:
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, Guid>(
BusinessDomainConstants.Database.People.ColumnNameForExtraPropertyIdentityUserPersonId,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder
.HasMaxLength(36)
.HasDefaultValue(null)
;
}
);
..however, removing this from the module startup does not change the error scenario.
HUZZAH! Thank-you @maliming you have provided the info I needed to solve this.
For future visitors trying to persist cryptographic key for IdentityServer4 in docker containers that are recreated at each deploy, the trick is to store a pfx cert file on the host that you load on app startup, along with a host mapping folder to store the generated keys (take a step further and encrypt the keys at rest).
I store the path and secret in ENV variables that are passed through docker-compose
Some code for you:
public class YourWebModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
PreConfigure<IIdentityServerBuilder>(builder =>
{
ConfigureIdentityCertificate(context, builder);
});
options.AddAssemblyResource(
...your assemblies...
);
});
}
private static void ConfigureIdentityCertificate(ServiceConfigurationContext context, IIdentityServerBuilder builder)
{
var configuration = context.Services.GetConfiguration();
var config = configuration.GetSection("Identity");
var certSecret = config["Secret"];
var certFilePath = config["CertificateStorePath"];
if (string.IsNullOrEmpty(certSecret))
{
Console.WriteLine("ERROR: No secret specified for identity cert!");
return;
}
if (string.IsNullOrEmpty(certFilePath) || !Directory.Exists(certFilePath))
{
Console.WriteLine("WARN: No specified CertificateStorePath or not found so falling back to content root.");
certFilePath = context.Services.GetHostingEnvironment().ContentRootPath;
}
var certFile = Path.Combine(certFilePath, "cert.pfx");
if (!File.Exists(certFile))
{
Console.WriteLine($"ERROR: Certificate file '{certFile}' not found on disk");
return;
}
X509Certificate2 cert;
try
{
cert = new X509Certificate2(
certFile,
certSecret,
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable
);
}
catch (Exception exception)
{
Console.WriteLine($"ERROR: Could not parse the identity cert! Message={exception.Message}");
return;
}
builder.AddSigningCredential(cert);
context.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(certFilePath));
}
Create a cert using the following commands (*nix):
openssl genrsa 2048
openssl req -x509 -days 3650 -new -key privatekey.pem -out publickey.pem
openssl pkcs12 -export -in publickey.pem -inkey privatekey.pem -out cert.pfx -password pass:$PASSWD
<-- $PASSWD var in .sh script
It is not! Should it be in there?
What's weird is that both tempkey.rsa and tempkey.jwk are set to "do not copy" yet the tempkey.jwk is found in the docker image.
Thank-you for your reply; let me see if I understand what you are saying.
The app generates cryptographic keys for protecting tokens, etc, when it starts. The app stores this key in memory by default (probably in redis by default with the standard template?) To ensure that the protected tokens can be read after the app restarts, the app must not be allowed to roll the keys; that is, the key must be perisisted to DB or to redis. This way, when the app starts up again, it will re-use the same key from previous and only roll it when it expires.
Do I have that correct?
I guess this doesn't explain the AbpUserTokens table or how to persist the tokens, but that wouldn't matter anyway unless the key was persisted. Do you know of any samples/examples of this setup?
Any idea how to configure this seeing as I am using the account module?
I am using docker containers to house my app and redis and reverse proxy in production. But every time I deploy a new build to the server the currently authorized users in my mobile app (and the web-app, but this is less troublesome) lose their authentication.
From looking at the account module code and the tables that are created (ie. AbpUserTokens) and the documentation, I think the default template should be using the IPersistedGrantStore that writes to EF Core database instead of in-memory store. But I cannot figure out how to tell the system to use the persisted store instead of in-memory one. I didn't want to try and separate the IdentityServer4 from the MVC project because of the complexity of doing so. Any directions or hints?
ABP Framework version: v4.2.2 UI type: MVC DB provider: EF Core Tiered (MVC) or Identity Server Separated (Angular): yes, tiered MVC Exception message and stack trace: Steps to reproduce the issue:
I am also working on trying to get the mobile app to re-up the auth when/if it is lost but this above scenario is bugging me because I THINK it should be straight forward to accomplish and would allow me to only concentrate on getting refresh token instead of checking for broken auth before every API call in the mobile app.
Perhaps just allowing the client to send cookies it received from the response is forcing the API system to think that RequestVerificationToken is required?? If I clear out cookies in POSTMAN then it does not have the requirement to include the RequestVerificationToken header.
Please confirm my understanding?
I am trying to work with multiple developers with API generated via template and they are complaining the cookie handling and XSRF token header is causing error 400 and redirects to the login page.
Configure<AbpAntiForgeryOptions>(options =>
{
options.AutoValidate = false;
});
[IgnoreAntiforgeryToken]
Errors I am seeing in the logs
[17:13:24 ERR] The required antiforgery header value "RequestVerificationToken" is not present.
[16:43:35 ERR] The provided antiforgery token was meant for a different claims-based user than the current user.
[16:43:44 ERR] The required antiforgery cookie ".AspNetCore.Antiforgery.9TtSrW0hzOs" is not present.
[16:44:18 ERR] The antiforgery cookie token and request token do not match.
[18:16:19 INF] Skipping the execution of current filter as its not the most effective filter implementing the policy Microsoft.AspNetCore.Mvc.ViewFeatures.IAntiforgeryPolicy
[18:19:05 ERR] The antiforgery token could not be decrypted.
[18:20:33 ERR] The provided antiforgery token was meant for a different claims-based user than the current user.