Hello together,
I hang unsuccessfully for days on this problem and not a single answer to different posts at different websites helped me so solve it.
I`m working on a Windows 10 System and implementing with VisualStudio 2017.
With AspNetCore I`ve implemented the following projects:
1.) Web.AuthServer: IdentityServer4 for authentication.
2.) Web.ApiServer: The first SignalR-Server.
3.) Web.ApiSwitch: The second SignalR-Server.
It has a HostedService with 2 SignalR-Clients as
a "bridge" between the two SignalR-Servers.>
The Web.ApiSwitch starts his HostedService which connects to itself and the Web.ApiServer including authentication at Web.AuthServer. This worked well as long as they ran with some "localhost:PORT" URL.
Now I`ve tried to run all the projects with "MyIP:PORT". The Web.AuthServer is using HTTPS together with a self signed certificate (generated with OpenSSL).
The certificate itself has beend build with the following command lines:
Generating private key:
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout IdentityServer4Auth.key -out IdentityServer4Auth.crt -subj "/CN=example.com" -days 3650
Generating the certificate:
openssl pkcs12 -export -out IdentityServer4Auth.pfx -inkey IdentityServer4Auth.key -in IdentityServer4Auth.crt -certfile IdentityServer4Auth.crt
The file has been added to mmc:
1.) File -> Add or Remove Snap-ins -> Certificates -> Add -> Computer Account -> OK
2.) Import the certificate (.cer) to personal -> Trusted Root Certification Authorities)
3.) Import the pfx, with exportable private key support, to personal -> certificates.
Code of Web.AuthServer:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, 5000, listenOptions =>
{
listenOptions.UseHttps();
});
})
.UseStartup<Startup>()
.ConfigureLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog();
})
.Build();
public void ConfigureServices(IServiceCollection services)
{
string csApplicationContext = Configuration.GetConnectionString("ApplicationContext");
string csConfigurationStore = Configuration.GetConnectionString("ConfigurationStore");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var settings = JsonFileManager<ServerSettings>.Load(AppDomain.CurrentDomain.BaseDirectory + "Config\\svConf.json");
services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
policy.WithOrigins(settings.CorsOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
services.AddAuthentication()
.AddJwtBearer(jwt =>
{
jwt.Authority = settings.JWTBearerSettings.Authority;
jwt.Audience = settings.JWTBearerSettings.Audience;
jwt.RequireHttpsMetadata = settings.JWTBearerSettings.RequireHttpsMetadata;
jwt.Validate();
});
services.AddPolicyServerClient(Configuration.GetSection("Policy"))
.AddAuthorizationPermissionPolicies();
services.AddDbContext<ApplicationDbContext>(builder =>
builder.UseSqlite(csApplicationContext, sqlOptions =>
sqlOptions.MigrationsAssembly(migrationsAssembly)));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddTransient<IClientStore, ClientService>();
var is4Builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseInformationEvents = true;
})
.AddConfigurationStore(options =>
options.ConfigureDbContext = builder =>
builder.UseSqlite(csConfigurationStore, sqlOptions =>
sqlOptions.MigrationsAssembly(migrationsAssembly)))
.AddClientStore<ClientService>()
.AddAspNetIdentity<ApplicationUser>();
SigninCredentialExtension.AddSigninCredentialFromConfig(is4Builder, Configuration.GetSection("SigninKeyCredentials"), Logger);
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
options.SslPort = 5000;
options.Filters.Add(new RequireHttpsAttribute());
});
}
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/Home/Error");
app.UseCors("default");
app.UseDefaultFiles();
app.UsePolicyServerClaims();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseIdentityServer();
await InitializeDbTestData.GenerateTestData(app);
app.UseMvcWithDefaultRoute();
}
public static class SigninCredentialExtension
{
private const string KeyType = "KeyType";
private const string KeyTypeKeyFile = "KeyFile";
private const string KeyTypeKeyStore = "KeyStore";
private const string KeyTypeTemporary = "Temporary";
private const string KeyFilePath = "KeyFilePath";
private const string KeyFilePassword = "KeyFilePassword";
private const string KeyStoreIssuer = "KeyStoreIssuer";
public static IIdentityServerBuilder AddSigninCredentialFromConfig(
this IIdentityServerBuilder builder, IConfigurationSection options, ILogger logger)
{
string keyType = options.GetValue<string>(KeyType);
logger.LogDebug($"SigninCredentialExtension keyType is {keyType}");
switch (keyType)
{
case KeyTypeTemporary:
logger.LogDebug($"SigninCredentialExtension adding Developer Signing Credential");
builder.AddDeveloperSigningCredential();
break;
case KeyTypeKeyFile:
AddCertificateFromFile(builder, options, logger);
break;
case KeyTypeKeyStore:
AddCertificateFromStore(builder, options, logger);
break;
}
return builder;
}
public static X509Certificate2 GetCertificateByThumbprint(string thumbprint)
{
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (certCollection.Count > 0) return certCollection[0];
}
return null;
}
private static void AddCertificateFromStore(IIdentityServerBuilder builder,
IConfigurationSection options, ILogger logger)
{
var keyIssuer = options.GetValue<string>(KeyStoreIssuer);
logger.LogDebug($"SigninCredentialExtension adding key from store by {keyIssuer}");
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindByIssuerName, keyIssuer, true);
if (certificates.Count > 0)
{
builder.AddSigningCredential(certificates[0]);
builder.AddValidationKey(certificates[0]);
}
else
logger.LogError("A matching key couldn't be found in the store");
}
private static void AddCertificateFromFile(IIdentityServerBuilder builder,
IConfigurationSection options, ILogger logger)
{
var keyFilePath = options.GetValue<string>(KeyFilePath);
var keyFilePassword = options.GetValue<string>(KeyFilePassword);
if (File.Exists(keyFilePath))
{
logger.LogDebug($"SigninCredentialExtension adding key from file {keyFilePath}");
builder.AddSigningCredential(new X509Certificate2(keyFilePath, keyFilePassword));
}
else
{
logger.LogError($"SigninCredentialExtension cannot find key file {keyFilePath}");
}
}
}
Code of Web.ApiServer:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, 5004, listenOptions =>
{
listenOptions.UseHttps();
});
})
.UseStartup<Startup>()
.ConfigureLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog();
})
.Build();
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
policy.WithOrigins(_settings.CorsOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
services.AddAuthentication(_settings.ISAuthenticationSettings.DefaultScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = _settings.ISAuthenticationSettings.Authority;
options.RequireHttpsMetadata = _settings.ISAuthenticationSettings.RequireHttpsMetadata;
options.ApiName = _settings.ISAuthenticationSettings.ApiName;
options.TokenRetriever = new Func<HttpRequest, string>(req =>
{
var fromHeader = TokenRetrieval.FromAuthorizationHeader();
var fromQuery = TokenRetrieval.FromQueryString();
return fromHeader(req) ?? fromQuery(req);
});
options.Validate();
});
services.AddSignalR(options => options.EnableDetailedErrors = true);
services.AddMvcCore(options =>
{
options.SslPort = 5003;
options.Filters.Add(new RequireHttpsAttribute());
})
.AddAuthorization()
.AddJsonFormatters();
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, ConnectionHost>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseCors("default");
app.UseSignalR(routes => routes.MapHub<EventHub>("/live"));
app.UseMvc();
}
Token request for SignalR clients:
public static async Task<TokenResponse> RequestTokenAsync(string authority, string clientID, string scope)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientID,
ClientSecret = "SomeTestSecret",
Scope = scope
});
if (response.IsError)
{
throw new Exception(response.Error);
}
return response;
}
The TokenRetriever of ConfigureServices from Web.ApiServer is just for getting the authentication of SignalR clients running, in due to the reason that they`re passing tokens via query string. It does the job.
Now the problem:
The clients of the HostedService of Web.ApiServer are trying to get the authentication token (jwt bearer) from Web.AuthServer but every time i get
the following exception:
System.Security.Authentication.AuthenticationException: 'The remote certificate is invalid according to the validation procedure.'
If I open the browser and type in the adress of Web.AuthServer "MyIP:5000" everything is working fine, after I accept the self signed certificate.
But the clients of the HostedService of Web.ApiServer can`t do this.
How do I ged rid of this exception and get some valid certificate? Am I missing something at client implementation? Hopefully someone can help me - getting stucked at this since more than 4 days.
What I have tried:
- Building different certificates with EasyRSA, OpenSSL, the tools from Windows Kits 10.
- Serveral code changes.. to much to write down here.