.netcore 3 client certificate with HttpClient - rest

I have a .netcore 3 project (WorkerService Template) which sends JSON data to a REST endpoint. The requests are sent via a HttpClient and configured to use a client certificate which the server requires. The server response is always 200 and HTML characters. According to the server managers the request is redirected to the home page of the web server, because the client machine is being correctly handled with a specific user but no certificate is available. I am using the following code:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("client").ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
X509Certificate2 certificate = GetCertificate(Configuration.CertificateSubjectKeyIdentifier);
handler.ClientCertificates.Add(certificate);
return handler;
}
}
GetCertificate retrieves the certificate from the Certificate Store:
private X509Certificate2 GetCertificate(string subjectIdentifier)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var collection = store.Certificates;
var certificates = collection.Find(X509FindType.FindBySubjectKeyIdentifier, subjectIdentifier, true);
foreach (var certificate in certificates)
{
if (DateTime.Compare(DateTime.Parse(certificate.GetExpirationDateString()), DateTime.Now) >= 0)
{
Logger.LogInformation($"Loaded X.509 certificate {certificate.Subject} issued by {certificate.Issuer}, valid from {certificate.GetEffectiveDateString()} to {certificate.GetExpirationDateString()}");
return certificate;
}
}
Logger.LogError($"X.509 certificate not loaded: No valid certificate could be found.");
return null;
}
Code which sends a request:
public async Task<ResponseData> PostAsync<T>(string url, T dataToSend)
{
ResponseData result = null;
HttpResponseMessage httpResponseMessage = null;
try
{
var errorHttp = false;
HttpClient httpClient;
using (httpClient = HttpClientFactory.CreateClient("client)) // IHttpClientFactory initialized in ctor
{
HttpContent httpContent;
using (httpContent = CreateJsonHttpContent(dataToSend, MediaType.ApplicationJson)) //build JSON from data
{
httpResponseMessage = await httpClient.PostAsync(url, httpContent).ConfigureAwait(false);
result = BuildResponseData(httpResponseMessage); //writes response data in a class
if (httpResponseMessage?.IsSuccessStatusCode == true)
{
result.Content = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
errorHttp = true;
}
if (errorHttp)
{
var httpRequestException = new HttpRequestException($"The http request to {url} was not successful.");
Logger.LogError($"{httpRequestException.Message} : {httpRequestException.InnerException}");
Logger.LogError(httpRequestException.StackTrace);
}
}
}
}
catch (SocketException socketException)
{
Logger.LogError($"{socketException.Message} : {socketException.InnerException}");
result = new ResponseData(socketException);
}
catch (WebException wex)
{
Logger.LogError($"{wex.Message} : {wex.InnerException}");
if (wex.Status == WebExceptionStatus.ConnectFailure || wex.Status == WebExceptionStatus.Timeout)
{
Logger.LogError($"Cannot connect to the rest service : {WebExceptionStatus.Timeout}");
}
}
catch (Exception ex)
{
LogException(ref ex);
result = new ResponseData(ex);
}
finally
{
httpResponseMessage?.Dispose();
}
return result;
}
The class which uses the PostAsync method is also registered in the ServiceCollection. Any ideas what could be wrong here? Could it also be that the certificate is not being handled correctly on the server side?

My strong suspection is the misconfiguration on client (your) end. Your application reads for certificate from LocalMachine store. By default, only local administrator and SYSTEM account can read/use private keys for certificates installed in LocalMachine store.
Either, install the certificate in CurrentUser store of a user account under which the client application is running, or explicitly grant private key permissions to user account under which the client application is running. To do this:
Open Certificates MMC snap-in under LocalMachine context (certlm.msc)
Expand Personal\Certificates
Select desired certificate, right-click and then Manage Private Keys menu item.
Grant Read permissions to user account under which the client application is running.
In this case, you don't need to modify your code or move certificate between stores.

Related

The authentication demand was rejected because the token had no audience attached

I want to authenticate the API resources using client credentials.
I have been able to generate the token successfully.
While sending the request for the API I logged the error and it says:
2021-06-10T00:47:19.1953056+05:45 [ERR] (OpenIddict.Validation.OpenIddictValidationDispatcher) The authentication demand was rejected because the token had no audience attached.
2021-06-10T00:47:19.1954307+05:45 [INF] (OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler) "OpenIddict.Validation.AspNetCore" was not authenticated. Failure message: "An error occurred while authenticating the current request."
2021-06-10T00:47:19.1960031+05:45 [INF] (OpenIddict.Validation.OpenIddictValidationDispatcher) The response was successfully returned as a challenge response: "{
\"error\": \"invalid_token\",
\"error_description\": \"The specified token doesn't contain any audience.\",
\"error_uri\": \"https://documentation.openiddict.com/errors/ID2093\"
}".
2021-06-10T00:47:19.1960852+05:45 [INF] (OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler) AuthenticationScheme: "OpenIddict.Validation.AspNetCore" was challenged.
What I am missing in my configuration? what is the correct way of using the client credentials grant type to secure the API resources with openiddict?
Resource Server Startup Configuration:
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
services.AddOpenIddict()
.AddValidation(options =>
{
options.SetIssuer("https://localhost:44301/");
options.AddAudiences("signal_system_web_resource");
options.UseIntrospection()
.SetClientId("signal_system_web_resource")
.SetClientSecret("846B62D0-DEF9-4215-A99D-86E6B8DAB342");
options.UseSystemNetHttp();
options.UseAspNetCore();
});
services.AddHttpClient();
return services;
}
The Client Configuration:
if (await manager.FindByClientIdAsync("nj-client") == null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "nj-client",
ClientSecret = "C4BBED05-A7C1-4759-99B5-0F84A29F0E08",
DisplayName = "Ninja Client Application",
Permissions =
{
Permissions.Endpoints.Token,
Permissions.GrantTypes.ClientCredentials
}
});
}
if (await manager.FindByClientIdAsync("signal_system_web_resource") == null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "signal_system_web_resource",
ClientSecret = "846B62D0-DEF9-4215-A99D-86E6B8DAB342",
Permissions =
{
Permissions.Endpoints.Introspection
}
};
await manager.CreateAsync(descriptor);
}
OpenIddictScopeDescriptor
var descriptor = new OpenIddictScopeDescriptor
{
Name = "signal.system.web",
Resources =
{
"signal_system_web_resource"
}
};
Resource Server API Controller
[Authorize]
[HttpGet("message")]
public async Task<IActionResult> GetMessage()
{
var identity = User.Identity as ClaimsIdentity;
if (identity == null)
{
return BadRequest();
}
return Content($"Signal System Web says that you have authorized access to resources belonging to {identity.Name}.");
}
Please help me through the error. any help or suggestion will be appreciated.
I am able to solve this problem by adding the resources while generating the token.
var principal = new ClaimsPrincipal(identity);
principal.SetResources("signal_system_web_resource");
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

WSO2IS JWT access token

I am trying get a JWT access token from WSO2 IS. I followed instructions from msf4j Oauth2 Security Sample, and managed to get a JWT acces token by resource owner password grant type.
but I have problem authenticating the token externally.
it seems that the token had not been signed by the default "wso2carbon.jks".
also, my claim configurations in the "service providers" was not reflected in jwt content
so my questions: how to config the JWT signing certificate in WSO2IS?
and also:
How to manipulate the claims in the JWT?
I do not want to turn to the "introspect" endpoint out of performance concern, and my strategy is to just trust the IS, only to make sure(locally) of the authenticity of the JWT token
please advise
thanks
You can follow [1] to get JWT Access Tokens(Self contained access tokens) using WSO2 Identity Server
[1] https://medium.com/#hasinthaindrajee/self-contained-access-tokens-with-wso2-identity-server-82111631d5b6
well, it seems to be my own fault.
I had been using the jose4j JWT package, and kept getting verification failed message.
after further checking into the msf4j implementation, I switched over to nimbus-jose-jwt JWT package, and got it done,
below are my implementation.
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class JwtParser {
private static final String KEYSTORE = System.getProperty("javax.net.ssl.trustStore");
private static final String KEYSTORE_PASSWORD = System.getProperty("javax.net.ssl.trustStorePassword");
private static Map<String, JWSVerifier> verifiers = getVerifiers();
public static JWTClaimsSet verify(String jwt) throws Exception {
SignedJWT signedJWT = SignedJWT.parse(jwt);
if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
new Exception("token has expired");
}
boolean notYet = true;
for(Iterator<JWSVerifier> it = verifiers.values().iterator(); notYet && it.hasNext();){
JWSVerifier verifier = it.next();
notYet = !signedJWT.verify(verifier);
}
if(notYet){
throw new Exception("token verification failed");
}
JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
if (claims == null) {
// Do something with claims
throw new Exception("non valid payload in token, failed");
}
return claims;
}
private static Map<String, JWSVerifier> getVerifiers(){
Map<String, JWSVerifier> verifiers = new HashMap<>();
try (InputStream inputStream = new FileInputStream(KEYSTORE)) {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(inputStream, KEYSTORE_PASSWORD.toCharArray());
Enumeration<String> aliases = keystore.aliases();
while(aliases.hasMoreElements()){
String alias = aliases.nextElement();
if(!keystore.isCertificateEntry(alias)){
continue;
}
Certificate cert = keystore.getCertificate(alias);
if(cert == null){
continue;
}
PublicKey key = cert.getPublicKey();
verifiers.put(alias, new RSASSAVerifier((RSAPublicKey)key));
}
}catch(KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e){
//TODO: report the exception
}
return verifiers;
}
}

Web API 2 use Windows Authentication for public users

How do I use Windows Authentication in WEB API for internal users who will also be on the public network? The REST API will be public facing and will need to authenticate intranet users as well as internet users. Basically, anybody not on Active Directory won't be able to access it and one more AD groups will be authorized.
The REST service at the moment has a security filter to validate token using attribute filter.
public class RestAuthorizeAttribute : AuthorizeAttribute
{
private const string SecurityToken = "token";
public override void OnAuthorization(HttpActionContext actionContext)
{
if (Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
private bool Authorize(HttpActionContext actionContext)
{
try
{
HttpRequestMessage request = actionContext.Request;
//Extract Token from the Request. This will work for all.
// E.g \api\Facilitiles\Token\298374u23lknndsjlkfds==
// \api\Ward\123\Token\298374u23lknndsjlkfds==
string path = request.RequestUri.LocalPath;
int indexOfToken = path.IndexOf(SecurityToken) + SecurityToken.Length + 1;
string token = path.Substring(indexOfToken);
bool isValid = SecurityManager.IsTokenValid(token, IpResolver.GetIp(request),request.Headers.UserAgent.ToString());
return isValid;
}
catch (Exception ex)
{
string av = ex.Message;
return false;
}
}
}
This is then applied to specific controllers like this:
[RestAuthorize]
[RoutePrefix("api/patient")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class PatientDetailsController : ApiController
{
PatientDetailsRetriever _patientDetailsRetriever;
// GET: api/patient/meds/personId/{personId}/token/{token}
[Route("meds/personId/{personId}/token/{token}")]
[HttpGet]
public HttpResponseMessage GetMeds(Int64 personId, string token)
{
List<Medication> meds;
.....
The client generates the token which includes username, password and domain and among other things.
Enabling Windows Authentication in IIS (web.config) will be enough to validate local users. But how does this work when the user is outside the network and sends in the credentials?
I have found the answer on this SO post.
//create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

Google API consent screen not showing up on after publishing to server

I am working with the Google Provisioning API. I have used Web Application type project from Google developer console. I have used Diamto blog and samples and it works perfectly on my local with all options like FileStore, Custom File Store, Service Account etc but when I uploaded on server user consent screen just doesn't pops up with any options like FileStore, Custom File Store. I spent days to figure out problem and solutions but nothing has worked for me so far.
my configuration
My server configuration is windows server 2008 datacenter r2,.net 4.5,IIS 7.5.
Service account works perfectly but I need to do it by Consent screen so Web Application type of project.
I have used google .net client library with version 1.9.2.27817.
I am just highlighting main code where it gets stuck and rest is same as per Diamto post and github examples.
Let me know if you need more info.
Code
public static DirectoryService AuthenticateOauth(string clientId, string clientSecret, string userName, IDataStore datastore)
{
string[] scopes = new string[] {DirectoryService.Scope.AdminDirectoryUser };
try
{
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, datastore).Result; // at this point it calls getasynch method for custom datasource
DirectoryService service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "GoogleProv",
});
return service;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
{
HttpClientInitializer = credential,
ApplicationName = "GoogleProv",
});
return service;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
///<summary>
// Returns the stored value for the given key or <c>null</c> if the matching file (<see cref="GenerateStoredKey"/>
// in <see cref="FolderPath"/> doesn't exist.
// </summary>
// <typeparam name="T">The type to retrieve</typeparam>
// <param name="key">The key to retrieve from the data store</param>
// <returns>The stored object</returns>
public Task<T> GetAsync<T>(string key)
{
//Key is the user string sent with AuthorizeAsync
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
// Note: create a method for opening the connection.
SqlConnection myConnection = new SqlConnection(myconn);
myConnection.Open();
// Try and find the Row in the DB.
using (SqlCommand command = new SqlCommand("select RefreshToken from GoogleUser where UserName = #username;", myConnection))
{
command.Parameters.AddWithValue("#username", key);
string RefreshToken = null;
SqlDataReader myReader = command.ExecuteReader();
while (myReader.Read())
{
RefreshToken = myReader["RefreshToken"].ToString();
}
if (RefreshToken == null )
{
// we don't have a record so we request it of the user.
tcs.SetResult(default(T)); // it comes here
}
else
{
try
{
// we have it we use that.
tcs.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize<T>(RefreshToken));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}
}
return tcs.Task; // it comes here and than gets hang forever
}
Any of your help is highly appreciated.

ServiceStack Authenticates both iOS Apps when one is logged in

I'm using the awesome ServiceStack to implement my REST backend which serves two iPhone apps written in Xamarin. Everything works great but i'm struggling in getting sessions to work correctly when the two apps are installed on the same device !
The issue is that if I login in one of the apps the second app gets authenticated and doesn't require me to login as a result of 'isCurrentUserAuthenticated()' method below.
I pass cookies with my requests to mimic the browser and to make sure user doesn't have to pass his credentials every time but I guess the problem is that maybe ServiceStack sees two authentication requests from the same IP so it authenticated them both using the first authentication requests succeeds.
Note : The two apps accesses the same database and UserAuth table but every app supports a user role different than the other.
The only way to fix it is to logout from the second app so the user can login again with his credentials to make everything work.
Can you please help with this ?
Here is the code so far :
public static class BLL
{
public static JsonServiceClient ServiceClient { get; set; }
public static string HostUri = "http://test.elasticbeanstalk.com";
public static string HostDomain = "test.elasticbeanstalk.com";
static BLL ()
{
string ss_id = ConfigRepository.GetConfigString ("ss-id");
string ss_pid = ConfigRepository.GetConfigString ("ss-pid");
ServiceClient = new JsonServiceClient (HostUri);
ServiceClient.CookieContainer.Add (new Cookie ("ss-id", ss_id, "/", HostDomain));
ServiceClient.CookieContainer.Add (new Cookie ("ss-pid", ss_pid, "/", HostDomain));
}
public static async Task<bool> isCurrentUserAuthenticated ()
{
bool result = false;
try {
Authenticate authRequest = new Authenticate ();
// Restore the cookie
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
NSUserDefaults.StandardUserDefaults.Synchronize ();
result = true;
} catch (Exception Ex) {
result = false;
}
return result;
}
public static async Task<AuthenticateResponse> Login (string userName, string password)
{
Authenticate authRequest = new Authenticate () {
provider = "credentials",
UserName = userName,
Password = password,
RememberMe = true,
};
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
var cookies = ServiceClient.CookieContainer.GetCookies (new Uri (HostUri));
if (cookies != null) {
var ss_id = cookies ["ss-id"].Value;
var ss_pid = cookies ["ss-pid"].Value;
if (!ss_id.IsNullOrEmpty ()) {
int r = ConfigRepository.AddConfigKey ("ss-id", ss_id);
System.Diagnostics.Debug.WriteLine ("ss-id " + ss_id.ToString ());
}
if (!ss_pid.IsNullOrEmpty ()) {
int r = ConfigRepository.AddConfigKey ("ss-pid", ss_pid);
System.Diagnostics.Debug.WriteLine ("ss-pid " + ss_pid.ToString ());
}
}
NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
NSUserDefaults.StandardUserDefaults.Synchronize ();
return response;
}
public static async Task<AuthenticateResponse> Logout ()
{
Authenticate authRequest = new Authenticate () {
provider = "logout"
};
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
return response;
}
}
The issue is because you're using the same Session Cookies with a shared ServiceClient instance which ends up referencing the same Authenticated Users Session.
ServiceStack Sessions are only based on the session identifiers (ss-id/ss-pid) specified by the clients cookies, if you use the same cookies you will be referencing the same Authenticated Users Session, they're not affected by IP Address or anything else.
If you want to authenticate as another user, use a new instance of the ServiceClient (so it's not using an existing Sessions Cookies).