SignalR and client certificate - asp.net-mvc

I plan to use SignalR to notify clients.
In production we are forced to use a client x509 certificate to authenticate the client using a reverse proxy. It will also terminate the SSL prior to connecting to our IIS webserver.
Will this work?
I can't find any api to add the client certificate or to use a WebRequestHandler.
I can't be the first.
/HAXEN

Regarding using client-based certificate: there is nothing in SingalR that allows to do it, I went through all the sources, and apparently it's an oversight, and a very annoying one. Here's a hack that I found:
public class MyHttpClient: IHttpClient
{
DefaultHttpClient _client = new DefaultHttpClient();
X509Certificate Certificate;
Action<IRequest> SpiceAction(Action<IRequest> prepareRequest)
{
return new Action<IRequest>(req =>
{
var fi = typeof(HttpWebRequestWrapper).GetField("_request",
BindingFlags.NonPublic | BindingFlags.Instance);
HttpWebRequest request = (HttpWebRequest) fi.GetValue(req);
request.ClientCertificates.Add(Certificate);
prepareRequest(req);
});
}
public Task<IResponse> Get(string url, Action<IRequest> prepareRequest)
{
return _client.Get(url, SpiceAction(prepareRequest));
}
public Task<IResponse> Post(string url, Action<IRequest> prepareRequest,
IDictionary<string, string> postData)
{
return _client.Post(url, SpiceAction(prepareRequest), postData);
}
}
and then instead of:
var conn = new Connection("https://xxxxxxxx");
conn.Start()
do:
var conn = new Connection("https://xxxxxxxx");
conn.Start(new MyHttpClient() { Certificate = xxxxxxx });

Here we go, with version 1.1 of SignalR Client Certificates are supported out of the box.
Great!

Related

Login to azure oauth2 with on premise adfs

I am trying to connect with oauth2 to our azure tenant inside some python script. I created an app registration and permitted some API access for it.
When I try to connect with username and password, I will just get an Error Code 50126 (Invalid username or password).
If I define some secret inside my app registration and switch to client secret as grant_type, I will have access to my app.
But I want to use username and password. Username is user#domain.com and password is correct, too.
So I think our ADFS server is making problems.
We are using some on premise AD and sync the user data to azure with Azure Connect, but we do not sync the passwords. So logins to Azure are forwarded to our adfs instance and are done on premise.
How can I implement that logic in my script? I need something like a redirect to adfs with my username and password and need the correct response to logon to azure.
I already searched a lot for this, but did not find an answer. It is not possible to me to activate the password sync.
My connection parameter to azure is like
tokenpost = {
'client_id':clientid,
'resource':crmorg,
'password':password,
'username':'user#domain.com',
'grant_type':'password'
}
tokenres = requests.post('https://login.microsoftonline.com/<tenantid>/oauth2/token', data=tokenpost)
Some had the same problem?
Best,
Robin
Got it.
It was necessary to get an assertion from ADFS first.
This was the doc which helped me a lot: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-saml-bearer-assertion
I wrote in Java now but it should be very equal in python:
package Azure
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import java.util.Base64;
String getAzureAccessToken(String clientId, String assertion, String azureURL)
{
HttpClient httpClient = new HttpClient();
PostMethod methodPost = new PostMethod(azureURL);
methodPost.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
methodPost.setRequestHeader("Host", "login.microsoftonline.com");
methodPost.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:saml2-bearer");
methodPost.addParameter("assertion", Base64.getEncoder().encodeToString(assertion.getBytes()));
methodPost.addParameter("client_secret", "XXX");
methodPost.addParameter("client_id", clientId);
methodPost.addParameter("scope", "XXX");
methodPost.addParameter("Accept", "application/json");
int returnCode = httpClient.executeMethod(methodPost)
if(returnCode != 200)
{
throw new Exception("Cannot connect to Azure "+methodPost.getStatusLine().toString())
}
BufferedReader br = new BufferedReader(new InputStreamReader(methodPost.getResponseBodyAsStream()));
String response;
while ((response = br.readLine()) != null) {
return response.split("\"access_token\":\"")[1].split("\"")[0];
}
}
String getAdfsAssertion(String username, String password, String adfsURL)
{
String adfsSoapXML = """<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header><a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:MessageID>urn:uuid:XXX</a:MessageID>
<a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>
<a:To s:mustUnderstand="1">"""+ adfsURL +"""</a:To>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" >
<o:UsernameToken u:Id="XXX">
<o:Username>"""+ username +"""</o:Username>
<o:Password>"""+ password +"""</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>urn:federation:MicrosoftOnline</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
<trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>
""";
HttpClient httpClient = new HttpClient();
PostMethod methodPost = new PostMethod(adfsURL);
methodPost.setRequestBody(adfsSoapXML);
methodPost.setRequestHeader("SOAPAction", "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue");
methodPost.setRequestHeader("Content-type", "application/soap+xml");
methodPost.setRequestHeader("client-request-id", "XXX");
methodPost.setRequestHeader("return-client-request-id", "true");
methodPost.setRequestHeader("Accept", "application/json");
int returnCode = httpClient.executeMethod(methodPost)
if(returnCode != 200)
{
throw new Exception("Cannot connect to adfs "+methodPost.getStatusLine().toString())
}
BufferedReader br = new BufferedReader(new InputStreamReader(methodPost.getResponseBodyAsStream()));
String response;
while ((response = br.readLine()) != null) {
return response.split("<trust:RequestedSecurityToken>")[1].split("</trust:RequestedSecurityToken>")[0];
}
}

IDX10503: Signature validation failed after updating to Owin.Security v 4.0.0

As per subject, I updated the Owin.Security.WsFederation and dependent packages to version 4.0 and I get the error.
I did not make any code changes other than changing
using Microsoft.IdentityModel.Protocols;
to
using Microsoft.IdentityModel.Protocols.WsFederation;
where is the WsFederationConfiguration class seems to be now.
Here is my StartupAuth:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
// Create WsFed configuration from web.config wsfed: values
var wsconfig = new WsFederationConfiguration()
{
Issuer = ConfigurationManager.AppSettings["wsfed:Issuer"],
TokenEndpoint = ConfigurationManager.AppSettings["wsfed:TokenEndPoint"],
};
/*
* Add x509 certificates to configuration
*
*/
// certificate.1 must always exist
byte[] x509Certificate;
x509Certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["wsfed:certificate.1"]);
wsconfig.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(x509Certificate)));
// certificate 2 may exist
if (ConfigurationManager.AppSettings["wsfed:certificate.2"] != null)
{
x509Certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["wsfed:certificate.2"]);
wsconfig.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(x509Certificate)));
}
// certificate 3 may exist
if (ConfigurationManager.AppSettings["wsfed:certificate.3"] != null)
{
x509Certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["wsfed:certificate.3"]);
wsconfig.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(x509Certificate)));
}
// Apply configuration to wsfed Auth Options
var wsoptions = new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Configuration = wsconfig,
Wreply = ConfigurationManager.AppSettings["wsfed:Wreply"],
Wtrealm = ConfigurationManager.AppSettings["wsfed:Wtrealm"],
};
wsoptions.TokenValidationParameters.NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn";
// Add WdFederation middleware to Owin pipeline
app.UseWsFederationAuthentication(wsoptions);
}
Is there something else 4.0 needs to validate the signature? I assume it's talking about the signature of the token from the issuer. I didn't see how to enable ShowPII to see what key it's looking at.
I am using MVC5 with the full framework. Not core.
Update:
I tried to modify the code to use the metadata provided by the identity provider in a properties file to create the WsFederationConfiguration and I still get the same error. I'm not sure what the Signature is, or where I get it from if it's not in the idp metadata.
Update2:
Here are the changes I made to use the wsfed metadata provided by the sts in a properties file. (I have removed the actual base64 encoded metadata, but needless to say it is the same XML you get when you regest the metadata from an STS that publishes it as and endpoint. As I said above, I get the same error:
public void ConfigureAuth(IAppBuilder app)
{
WsFederationConfiguration wsconfig;
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
var metaDataDocument = System.Text.Encoding.UTF8.GetString(
Convert.FromBase64String("...c2NyaXB0b3I+"));
using (var metaDataReader = XmlReader.Create(new StringReader(metaDataDocument), SafeSettings))
{
wsconfig = (new WsFederationMetadataSerializer()).ReadMetadata(metaDataReader);
}
// Apply configuration to wsfed Auth Options
var wsoptions = new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Configuration = wsconfig,
Wreply = ConfigurationManager.AppSettings["wsfed:Wreply"],
Wtrealm = ConfigurationManager.AppSettings["wsfed:Wtrealm"],
};
wsoptions.TokenValidationParameters.NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn";
// Add WdFederation middleware to Owin pipeline
app.UseWsFederationAuthentication(wsoptions);
}
I worked with some folks on the team at MS. The issue here was that our STS is using SHA1 to sign the token and the new version of weFederation doesn't support SHA1 as it is not-secure and is deprecated.
The easiest way to use WIF with owin is through the usage of the federation meta data (which lives at FederationMetadata/2007-06/FederationMetadata.xml). Then you don't need to setup anything at all which is explained in Configure claims based web applications using OWIN WsFederation middleware . The precondition is of course that your STS publishes a meaningful FederationMetaData document. The nice advantage is that your public keys needed for validation are automatically picked up by your application (and renewing them is done seamlessly).
This is IMHO that is much easier than the approach you are taking.
You can follow Manual configuration of OWIN WS-Federation Identity provider as it describes a more easy way than yours.

Azure notification hub installation c#

Can anyone help me with Azure notification hub, how to set up device installation form c# code. I have problem with the Installation object. How to set it to pass it as parameter to CreateOrUpdateInstallation method of hub client instance. It's not clear to me.
I have a hub on azure that works with device registration like charm in local, but uploaded on azure are not working. Now I wanna try with istalation.
thnx
update: after 4 days, I figured out, that you can't send notification to yourself. Azure somehow knows that you are sending notification to yours phone, and that's why my welcome message never delivered to my phone.
update: this is how now I install the device i my backend code:
[HttpGet]
[Route("api/push/test-installation")]
public async Task<IActionResult> NotificationInstalationTest()
{
string connectionString = "{{my connection string}}";
string hubName = "{{my hub name}}";
string token = "{{tokne}}";
NotificationHubClient hubClient = NotificationHubClient.CreateClientFromConnectionString(connectionString, hubName);
string notificationText = $"Test message for Azure delivery for Atila at: {DateTime.Now.ToShortTimeString()}";
var alert = new JObject
(
new JProperty("aps", new JObject(new JProperty("alert", notificationText))),
new JProperty("inAppMessage", notificationText)
).ToString(Newtonsoft.Json.Formatting.None);
IList<string> tags = new List<string>();
tags.Add("email");
IDictionary<string, string> pushVariables = new Dictionary<string, string>();
pushVariables.Add( "email", "atila#panonicit.com" );
Installation installation = new Installation();
installation.InstallationId = Guid.NewGuid().ToString();
installation.Platform = NotificationPlatform.Apns;
installation.PushChannel = token;
installation.Tags = tags;
installation.PushVariables = pushVariables;
await hubClient.CreateOrUpdateInstallationAsync(installation);
NotificationOutcome result = await hubClient.SendAppleNativeNotificationAsync(alert);
return Ok("Success");
}
Now when I hit this endpoint with Postman it works, if the same endpoint call comes from iOS it not works!
thnx
How to set it to pass it as parameter to CreateOrUpdateInstallation method of hub client instance. It's not clear to me.
Based on my understanding, you are registering the notification hub from your backend using the installation model. For your WebAPI project, assuming your method for create/update an installation as follows:
InstallationController.cs
//PUT api/installation
public async Task<HttpResponseMessage> Put(DeviceInstallation deviceUpdate)
{
Installation installation = new Installation();
installation.InstallationId = deviceUpdate.InstallationId;
//TODO:
await hub.CreateOrUpdateInstallationAsync(installation);
return Request.CreateResponse(HttpStatusCode.OK);
}
For your mobile client, you could refer to the following method:
private async Task<HttpStatusCode> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation)
{
using (var httpClient = new HttpClient())
{
//TODO: set your authorization header
//httpClient.DefaultRequestHeaders.Authorization
var putUri =$"{your-backend-endpoint}/api/installation";
string json = JsonConvert.SerializeObject(deviceInstallation);
var response = await httpClient.PutAsync(putUri, new StringContent(json, Encoding.UTF8, "application/json"));
return response.StatusCode;
}
}
Moreover, for more details you could refer to Registration management and here for building backend with the registration model to build your backend using the installation model.

MVC accessing external Web API using login credentials

In need of some help accessing an external Web API passing along credentials in order to access the methods available. I have included the code below that i use in order to attempt to access the Web API. However, i receive the following error every time i attempt to access it:
"The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel."
What am i missing or what am i doing wrong? I have been circling around this for a couple days and have tried a couple different techniques but continue to get the same error. Here is one technique that i used.
private static async Task<string> GetAPIToken(string userName, string password, string apiBaseUri)
{
try
{
using (var client = new HttpClient())
{
//setup client
client.BaseAddress = new Uri(apiBaseUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//setup login data
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("username",userName),
new KeyValuePair<string,string>("password",password),
});
//send request
HttpResponseMessage responseMessage = await client.PostAsync("Token", formContent);
//get access token from response body
var responseJson = await responseMessage.Content.ReadAsStringAsync();
var jobject = JObject.Parse(responseJson);
return jobject.GetValue("access_token").ToString();
}
}
catch (Exception ex)
{
return null;
}
}
Any help would be greatly appreciated.
Thanks
There is a little bit of a difference when using HTTPS vs HTTP. This question should give you the information you need to fix your problem.
Make Https call using HttpClient

Sharing IClaimsPrincipal/FedAuth Cookie between servers/apps ID1006

I have an ASP.NET app that uses Azure ACS (and indirectly ADFS) for Authentication - which all works fine. Now I've been asked to pass the SessionToken to another backend service where it can be verified and the claims extracted. [Long Story and not my choice]
I'm having fits on the decryption side, and I'm sure I'm missing something basic.
To set the stage, the error upon decryption is:
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
The ASP.NET website uses the RSA wrapper ala:
void WSFederationAuthenticationModule_OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
string thumbprint = "BDE74A3EB573297C7EE79EB980B0727D73987B0D";
X509Certificate2 certificate = GetCertificate(thumbprint);
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(certificate),
new RsaSignatureCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
(the thumbprint is the same value as added by FedUtil in web.config.
I write the token with:
if (Microsoft.IdentityModel.Web.FederatedAuthentication.SessionAuthenticationModule.TryReadSessionTokenFromCookie(out token))
{
Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler th = new Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler();
byte[] results = th.WriteToken(token);
...
which gives me:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_53382b9e-8c4b-490e-bfd5-de2e8c0f25fe-94C8D2D9079647B013081356972DE275"
xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:54bd1bd7-1110-462b-847e-7f49c1043b32</Identifier>
<Instance>urn:uuid:0462b7d7-717e-4ce2-b942-b0d6a968355b</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">AQAAANCMnd blah blah 1048 bytes total
</Cookie>
</SecurityContextToken>
and, with the same Certificate on the other box (and the token read in as a file just for testing), I have:
public static void Attempt2(FileStream fileIn, X509Certificate2 certificate, out SecurityToken theToken)
{
List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaSignatureCookieTransform(certificate),
new RsaEncryptionCookieTransform(certificate)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
// setup
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(certificate);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
using (var reader = XmlReader.Create(fileIn))
{
theToken = sessionHandler.ReadToken(reader);
}
}
and then ReadToken throws a FormatException of
ID1006: The format of the data is incorrect. The encryption key length is negative: '-724221793'. The cookie may have been truncated.
At this point, I can't tell if my overall approach is flawed or if I'm just missing the proverbial "one-line" that fixes all of this.
Oh, and I'm using VS2010 SP1 for the website (.NET 4.0) and I've tried both VS2010SP1 .NET 4.0 and VS2012 .NET 4.5 on the decoding side.
Thanks!
Does your app pool account for the backend service have read access to the certificate? If not give your app pool account for the backend service read access to the certificate. I had problems in the past with encryption/decryption because of this.
This might help, this will turn your FedAuth cookies into a readable XML string like:
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_548a372e-1111-4df8-b610-1f9f618a5687-953155F0C35B4862A5BCE4D5D0C5ADF0" xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512">
<Identifier>urn:uuid:c9f9b733-1111-4b01-8af3-23c8af3e19a6</Identifier>
<Instance>urn:uuid:ee955207-1111-4498-afa3-4b184e97d0be</Instance>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">long_string==</Cookie>
</SecurityContextToken>
Code:
private string FedAuthToXmlString(string fedAuthCombinedString)
{
// fedAuthCombinedString is from FedAuth + FedAuth1 cookies: just combine the strings
byte[] authBytes = Convert.FromBase64String(fedAuthCombinedString);
string decodedString = Encoding.UTF8.GetString(authBytes);
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var thumbprint = "CERT_THUMBPRINT"; // from config
var cert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false)[0];
var sessionTransforms = new List<System.IdentityModel.CookieTransform>(new System.IdentityModel.CookieTransform[]
{
new System.IdentityModel.DeflateCookieTransform(),
new System.IdentityModel.RsaSignatureCookieTransform(cert),
new System.IdentityModel.RsaEncryptionCookieTransform(cert)
});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
SecurityTokenResolver resolver;
{
var token = new X509SecurityToken(cert);
var tokens = new List<SecurityToken>() { token };
resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
}
sessionHandler.Configuration = new SecurityTokenHandlerConfiguration();
sessionHandler.Configuration.IssuerTokenResolver = resolver;
var i = 0; // clear out invalid leading xml
while ((int)decodedString[i] != 60 && i < decodedString.Length - 1) i++; // while the first character is not <
store.Close();
return decodedString.Substring(i);
}

Resources