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:
- Spin up new solution
- See the AbpUserTokens table is created
- Use OAuth system (ie, with the react-native template) to login a user
- See the AbpUserTokens table is empty
- Restart the server and lose your authentication
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.
8 Answer(s)
-
0
hi
You should always persist the key to the database or redis, etc. If you use redis, make sure that the key is not cleared;
https://docs.identityserver.io/en/latest/topics/startup.html#key-material https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0
-
0
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?
-
0
hi
The AbpUserTokens table has nothing to do with user login. It's represents an authentication token for a user.
Maybe you can check if
tempkey.rsa
exists in your docker?https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/tempkey.rsa
-
0
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.
-
0
hi
Please try to use AddSigningCredential and check your Data Protection.
https://docs.identityserver.io/en/latest/topics/startup.html#key-material https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0
-
0
-
0
https://docs.abp.io/en/abp/4.4/Modules/IdentityServer#abpidentityserverbuilderoptions
public override void PreConfigureServices(ServiceConfigurationContext context) { PreConfigure<IIdentityServerBuilder>(builder => { builder.AddSigningCredential(...); }); }
-
0
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