Hi,
I have the following code on the client :
var handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = PinPublicKey;
var url = $"https://{host}:44310/connect/token";
var cert = SmartCardHandler.Instance.TriggerCertificateFetch(true);
handler.ClientCertificates.Add(cert);
return new TokenClient(url, ClientTypes.Siths, "x", handler);
This will fetch a local certificate(from a smartcard) on the client and bind it to the TokenClient, then it will connect to the IdentityService that has its own fuction certificate(2 way certificate security).
Right now the user is setup like this :
ClientId = ClientTypes.Siths,
ClientName = "SITHS client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AccessTokenType = AccessTokenType.Reference,
ClientSecrets = new List<Secret>
{
new Secret("x".Sha256())
},
AllowedScopes = new List<string>
{
"My.wcf",
"offline_access"
}
This makes me end up in my ResourceOwnerPasswordValidator.
The question is how I get the client certificate from here? I need information from the certificate to be able to find the specific user.
I think an extension grant type would be the best way to authenticate the user using an certificate?
http://docs.identityserver.io/en/release/topics/grant_types.html
Yes, that sounds like a better way to go. I have implmented this :
public class CustomExtensionGrantValidator : IExtensionGrantValidator
{
public string GrantType => "siths";
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userToken = context.Request.Raw.Get("token");
context.Result = new GrantValidationResult("", "test");
return;
}
}
new Client
{
ClientId = ClientTypes.Siths,
ClientName = "SITHS client",
AllowedGrantTypes = GrantTypes.List("siths"),
AccessTokenType = AccessTokenType.Reference,
ClientSecrets = new List<Secret>
{
new Secret("x".Sha256())
},
AllowedScopes = new List<string>
{
"My.wcf",
"offline_access"
}
},
services.AddTransient<IExtensionGrantValidator, CustomExtensionGrantValidator>()
Also tried this
services.AddIdentityServer().AddInMemoryClients(Config.GetClients())
.AddInMemoryApiResources(Config.GetApiResources())
.AddOperationalStore(builder =>
builder.UseSqlServer(identitySqlConnection, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddExtensionGrantValidator<CustomExtensionGrantValidator>();
But the ValidateAsync is never triggered? Instead I get this :
fail: IdentityServer4.Validation.TokenRequestValidator[0]
ro.siths.my not authorized for resource owner flow
fail: IdentityServer4.Validation.TokenRequestValidator[0]
{
"ClientId": "ro.siths.my",
"ClientName": "SITHS client",
"GrantType": "password",
"Raw": {
"grant_type": "password",
"username": "test",
"password": "REDACTED",
"scope": "my.wcf"
}
}
Client is creating the tookenclient like this :
var handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = PinPublicKey;
var url = $"https://{host}:44310/connect/token";
var cert = SmartCardHandler.Instance.TriggerCertificateFetch(true);
handler.ClientCertificates.Add(cert);
return new TokenClient(url, ClientTypes.Siths, "x", handler);
Any clue to why the ValidateAsync is never triggered?
Yes in a way, I need the IdentityService to validate the client certificate and then let me manually grant the user, in this case with data from the certificate.
So when I get to the ValidateAsync the client certificate should alread have been checked, if not there should be some way to check that its correct.
Is it true that this is not yet working in IdentityServer4? I could not find much about it in the online documentation.
I am setting a certificate for the TokenClient but I suspect that it is never used, how do i know?
As far as I know it should be working in IdentityServer4 in the current version. If you get the extension grant type working, then you should be able to get the client certificate from the request/pipeline in there and also validate the certificate according to your user store or other business rules.
Could you please attach the code how the client calls IdentityServer as well? Right now you have only included how you create the TokenClient.
Oh well, it looks like you are still requesting the token with grant type password according to this:
fail: IdentityServer4.Validation.TokenRequestValidator[0]
{
"ClientId": "ro.siths.my",
"ClientName": "SITHS client",
"GrantType": "password",
"Raw": {
"grant_type": "password",
"username": "test",
"password": "REDACTED",
"scope": "my.wcf"
}
Try change how you call IdentityServer from the client, like how it's done in the docs:
return await client.RequestCustomGrantAsync("[your extension grant type]", "api2", payload);
http://docs.identityserver.io/en/release/topics/extension_grants.html#refextensiongrants
Hi,
I have now manage to get to the IExtensionGrantValidator.ValidateAsync, this is how the client connects :
internal override TokenClient GetTokenClient(string host, string port)
{
var handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = PinPublicKey;
var url = $"https://{host}:44310/connect/token";
var cert = SmartCardHandler.Instance.TriggerCertificateFetch(true);
handler.ClientCertificates.Add(cert);
return new TokenClient(url, ClientTypes.Siths, "x", handler);
}
public static bool PinPublicKey(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors ||
sslPolicyErrors == SslPolicyErrors.RemoteCertificateNotAvailable)
throw new CommunicationException(SslPolicyErrors.RemoteCertificateNotAvailable.ToString());
return true;
}
public async Task<MyToken> GetToken(string username, string password, LoginTypeBase loginType)
{
_loginType = loginType;
MyToken result = newMyToken();
var tokenClient = await GetTokenClient();
var token = await tokenClient.RequestCustomGrantAsync("siths", scope: "my.wcf");
//var token = await tokenClient.RequestResourceOwnerPasswordAsync(username, password, scope: "my.wcf");
if (token == null || token.AccessToken == null)
throw ValidationContainer.FromValidationKey(ValidationKey.Inloggning_OgiltigtNamnLosen);
result.Token = token.AccessToken;
_refreshToken = token.RefreshToken;
if (_myToken == null)
{
var _ = AsyncRecurringTask.Run(async () => await RefreshToken(), TimeSpan.FromMinutes(30), _cancelToken.Token);
}
_myToken = result;
result.ClientVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
return result;
}
I have tried to find a certificate in the IExtensionGrantValidator.ValidateAsync with no luck, still not sure if it is really using the certificate while connecting to IdentityService
info: IdentityServer4.Validation.TokenRequestValidator[0]
Token request validation success
{
"ClientId": "ro.siths.my",
"ClientName": "SITHS client",
"GrantType": "siths",
"Scopes": "my.wcf",
"Raw": {
"grant_type": "siths",
"scope": "my.wcf"
}
}
You need to inject the IHttpContextAccessor and get the client cert from the context.
Thanks now I got the HttpContext set! the HttpContext.Connection.ClientCertificate is however null while executing ValidateAsync? I can see that the certificate is set on the handler in the client before connecting to the IdentityServer but it seems like it麓s never used?
This is how the CustomExtensionGrantValidator looks like :
public class CustomExtensionGrantValidator : IExtensionGrantValidator
{
public readonly IAnvandareRepository AnvandareRepository;
public readonly IEncryptionService EncryptionService;
public readonly IHttpContextAccessor HttpContextAccessor;
public CustomExtensionGrantValidator(IAnvandareRepository anvandareRepository, IEncryptionService encryptionService, IHttpContextAccessor httpContextAccessor)
{
AnvandareRepository = anvandareRepository;
EncryptionService = encryptionService;
HttpContextAccessor = httpContextAccessor;
}
public string GrantType => "siths";
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
context.Result = new GrantValidationResult(subject.person_id.ToString(), "pwd");
return;
}
}
Im still thinking that I do something wrong in the client code(pasted in my last post) becouse my third party software should react when the client tries to connect to the IdentityServer with the specific certificate.
I can see that the HttpContextAccessor.HttpContext.Connection.RemotePort is 58205 whiel the HttpContextAccessor.HttpContext.Connection.LocalPort is set to 44310. Does this sound right? The service will communicate with the client on another port?
I never tried it myself. Probably involves some config on both IIS and the IIS integration middleware.
Okay, This is selfhosted so no IIS at all in this case. Could you point me where I should read up?
I'm going to look into the same thing soon, but maybe this could be some help? http://stackoverflow.com/questions/38665934/asp-net-core-kestrel-server-ssl-client-certificate-only-for-specific-routes-or-c
https://github.com/aspnet/KestrelHttpServer/issues/332
I guess it is because the web server doesn't "accept" client certificates and because of that the ClientCertification-property is null.
Thanks alot! This(HttpsConnectionFilterOptions) solved my problem :
var host = new WebHostBuilder()
.UseKestrel(options=>
{
HttpsConnectionFilterOptions httpsoptions = new HttpsConnectionFilterOptions();
httpsoptions.ServerCertificate = Startup.GetServerCertificate();
httpsoptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
httpsoptions.CheckCertificateRevocation = false;
options.UseHttps(httpsoptions);
})
.UseUrls($"http://{Address}:{Port}", $"https://{Address}:44310")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
No the client is using the certificate and I can read it in the IdentityService.
Great! Please close it if you don't have any additional question :)
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.