I have the following components:
WPF Application,
Identity Server,
WCF Web Service,
WPF Application uses WebBrowser control to authenticate using Thintecture Identity Server using WS-Federation. Identity Server has enabled Home Realm Discovery and allow authentication using Facebook, Live ID and Google. After authentication I get ReqquestSecurityTokenResponse message, which I convert into SecurityToken.
After getting this SecurityToken I want to call WebService. I think I need create ActAsToken issued again by Thintecture Identity Server, but I can't configure it.
var serviceAddress = "http://localhost:7397/Service1.svc";
var token3 = token2.ToSecurityToken();
var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.Message);
binding.Security.Message.IssuedKeyType = System.IdentityModel.Tokens.SecurityKeyType.SymmetricKey;
binding.Security.Message.IssuerAddress = new EndpointAddress("https://dev3.example.com/Identity/issue/wsfed");
binding.Security.Message.IssuerBinding = new WS2007HttpBinding();
var factory = new ChannelFactory<IService1Channel>(binding,
new EndpointAddress(
new Uri(serviceAddress),
new DnsEndpointIdentity("dev3.example.com")));
factory.Credentials.SupportInteractive = false;
var proxy = factory.CreateChannelWithActAsToken(token3);
{
try
{
var output = proxy.GetData(1);
MessageBox.Show(output);
proxy.Dispose();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
But I get exceptions.
WebService is configured using Identity and access... VS extension.
Is this scenario possible?
you don't need an ActAs - you can use the CreateChannelWithIssuedToken method to create your WCF proxy.
You also need to configure bearer keys on the WCF service and client (instead of SymmetricKey).
Related
I have an ASPNET Core 2 application which I am trying to Authenticate with Azure AD using OpenId. I just have boilerplate code from selecting Single Organization Authentication in the ASPNET Core 2 templates, so no custom code. I followed the article here.
The app is not able to get metadata from the Azure AD application because of proxy. The same URL returns data if I just paste it in browser.
The error I get is:
HttpRequestException: Response status code does not indicate success: 407 (Proxy Authentication Required).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
IOException: IDX10804: Unable to retrieve document from: 'https://login.microsoftonline.com/my-tenant-id/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+d__8.MoveNext()
I have another ASPNET 4.5.2 application where I am able to perform authentication with the same Azure AD app as above after setting proxy in code like below:
System.Net.HttpWebRequest.DefaultWebProxy = new WebProxy
{
Address = new Uri("http://my-company-proxy:8080"),
Credentials = new NetworkCredential
{
UserName = "proxyusername",
Password = "proxypassword"
}
};
So Essentially my problem is to get past the Proxy Authentication in ASPNET Core 2.
I have tried Microsoft.AspNetCore.Proxy package. Its pretty much broken and doesn't work for me. Also I tried adding the Proxy entries in machine.config (which are actually not required for 4.5.2 app) but that doesn't work as well. I believe getting past a corporate proxy should be very trivial, but doesn't look like it so far.
Tratcher's comment pointed me in the right direction and I got it working, but just to help everyone with it, below is what you need to do:
builder.AddOpenIdConnect(options => options.BackchannelHttpHandler = new HttpClientHandler
{
UseProxy = true,
Proxy = new WebProxy
{
Credentials = new NetworkCredential
{
UserName = "myusername",
Password = "mypassword"
},
Address = new Uri("http://url:port")
}
});
In Full .net framework setting up a proxy is using a config setting
entry but to use an HTTP proxy in .net core ,you have to implement
IWebProxy interface.
Microsoft.AspNetCore.Proxy is proxy middleware which serves a different purpose (to setup reverse proxy) not as an http proxy .Refer this article for more details
To implement a webproxy in .net core,
public class MyHttpProxy : IWebProxy
{
public MyHttpProxy()
{
//here you can load it from your custom config settings
this.ProxyUri = new Uri(proxyUri);
}
public Uri ProxyUri { get; set; }
public ICredentials Credentials { get; set; }
public Uri GetProxy(Uri destination)
{
return this.ProxyUri;
}
public bool IsBypassed(Uri host)
{
//you can proxy all requests or implement bypass urls based on config settings
return false;
}
}
var config = new HttpClientHandler
{
UseProxy = true,
Proxy = new MyHttpProxy()
};
//then you can simply pass the config to HttpClient
var http = new HttpClient(config)
checkout https://msdn.microsoft.com/en-us/library/system.net.iwebproxy(v=vs.100).aspx
We have a web app built on Asp.Net core. It doesn't contain any authentication middleware configured in it.
We are hosting on Azure App Service and using the Authentication/Authorization option (EasyAuth) to authenticate against Azure AD.
The authentication works well - we get the requisite headers inserted and we can see the authenticated identity at /.auth/me. But the HttpContext.User property doesn't get populated.
Is this a compatibility issue for Asp.Net core? Or am I doing something wrong?
I've created a custom middleware that populates the User property until this gets solved by the Azure Team.
It reads the headers from the App Service Authentication and create a a user that will be recognized by the [Authorize] and has a claim on name.
// Azure app service will send the x-ms-client-principal-id when authenticated
app.Use(async (context, next) =>
{
// Create a user on current thread from provided header
if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
{
// Read headers from Azure
var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];
// Create claims id
var claims = new Claim[] {
new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
};
// Set user in current context as claims principal
var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
identity.AddClaims(claims);
// Set current thread user to identity
context.User = new GenericPrincipal(identity, null);
};
await next.Invoke();
});
Yes, this is a compatibility issue. ASP.NET Core does not support flowing identity info from an IIS module (like Easy Auth) to the app code, unfortunately. This means HttpContext.User and similar code won't work like it does with regular ASP.NET.
The workaround for now is to invoke your web app's /.auth/me endpoint from your server code to get the user claims. You can then cache this data as appropriate using the x-ms-client-principal-id request header value as the cache key. The /.auth/me call will need to be properly authenticated in the same way that calls to your web app need to be authenticated (auth cookie or request header token).
I wrote a small basic middleware to do this. It will create an identity based off of the .auth/me endpoint. The identity is created in the authentication pipeline so that [authorize] attributes and policies work with the identity.
You can find it here:
https://github.com/lpunderscore/azureappservice-authentication-middleware
or on nuget:
https://www.nuget.org/packages/AzureAppserviceAuthenticationMiddleware/
Once added, just add this line to your startup:
app.UseAzureAppServiceAuthentication();
The following code decrypts the AAD token from the Azure App Service HTTP header and populates HttpContext.User with the claims. It's rough as you'd want to cache the configuration rather than look it up on every request:
OpenIdConnectConfigurationRetriever r = new OpenIdConnectConfigurationRetriever();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.Endpoint, r);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys.ToList(),
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateLifetime = true,
ClockSkew = new TimeSpan(0, 0, 10)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
string token = context.Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
if (!String.IsNullOrWhiteSpace(token))
{
principal = handler.ValidateToken(token, tokenValidationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null) { throw new ArgumentException("Invalid JWT"); }
if (principal != null)
{
context.User.AddIdentities(principal.Identities);
}
}
It only works for Azure AD. To support other ID providers (Facebook, Twitter, etc) you'd have to detect the relevant headers and figure out how to parse each provider's token. However, it should just be variations on the above theme.
You can give this library a try. I faced a similar problem and created this to simplify the use.
https://github.com/dasiths/NEasyAuthMiddleware
Azure App Service Authentication (EasyAuth) middleware for ASP.NET
CORE with fully customizable components with support for local
debugging
It hydrates the HttpContext.User by registering a custom authentication handler. To make things easier when running locally, it even has the ability to use a json file to load mocked claims.
I have two applications, one web-api application (y.x.com) and a front-end application (z.x.com). To authenticate the user who visits z.x.com I use ws-federation or microsoft live login following the web api template code provided by visual studio 2015. If I talk directly to the web api application (y.x.com) from my browser, postman, fiddler or anything similar the authentication works fine but if I try to sign in from the front-end application I get error: invalid_request (status 400).
Now I wonder if it should be possible to sign in from application z.x.com by calling y.x.com/Account/ExternalLogin?provider=Federation&response_type=token&client_id=self&redirect_uri=http://y.x.com.
My startup.auth in y.x.com looks like this
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
var wsOptions = new WsFederationAuthenticationOptions
{
MetadataAddress = "https://login.microsoftonline.com/afd2d5a6-bdb1-43f8-a42b-83ec49f1f22d/federationmetadata/2007-06/federationmetadata.xml",
Wtrealm = "http://y.x.com/",
Notifications = new WsFederationAuthenticationNotifications()
};
app.UseWsFederationAuthentication(wsOptions);
I can provide more code but I'm mostly interested in if should work at all.
Thanks.
This is possible. After som digging and help it turns out that in the web-api template there is a method named ValidateClientRedirectUri in the class ApplicationOAuthProvider. If I change that method to
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
and then from my front end application I can now have any return url I want, making it possible to sign in from the front-end application via the web-api application to an external source.
I'm writing a client against a customer's SOAP service, using WCF.
We've had a number of go-arounds trying to get the authentication working. I ended up using a Custom Binding, because some random guy on the web said that BasicHttpBinding didn't support the necessary security options, and WsHttpBinding didn't support SOAP 1.1, which is what they are using.
So, what I have:
var message = this.constructMessagecollection);
if (message != null)
{
var ea = new EndpointAddress(this.webServiceUrl);
var binding = new CustomBinding();
binding.Elements.Add(new TextMessageEncodingBindingElement(
MessageVersion.Soap11, Encoding.UTF8));
binding.Elements.Add(new HttpsTransportBindingElement { AuthenticationScheme = System.Net.AuthenticationSchemes.Basic });
using (var client = new CustomersWebserviceClient(binding, ea))
{
if (!String.IsNullOrWhiteSpace(this.webServiceUsername) && !String.IsNullOrWhiteSpace(this.webServicePassword))
{
var credentials = client.ClientCredentials.UserName;
credentials.UserName = this.webServiceUsername;
credentials.Password = this.webServicePassword;
}
var result = client.ReceiveMessage(message);
log.writeLine(String.Format("Call to client.ReceiveMessage() returned {0}", result));
}
return true;
}
Now, I've been asked if I can configure my client to do preemptive authentication. I've done some web browsing, and not found much. And I'm at a loss as to how to integrate what little I've found into my current code.
I don't think you can configure WCF to pre authenticate. Your options are to add the headers manually to each request or to build a message inspector to do it and configure it once. Either way those settings are not related to the binding. I guess you could write your own custom http transport (that internally uses the regular http transport) and add it there but not sure it worth the effort. As described here, to add it manually you can use:
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes(client.ClientCredentials.UserName.UserName + ":" +
client.ClientCredentials.UserName.Password));
using (OperationContextScope scope = new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] =
httpRequestProperty;
// Invoke client
}
As for the second option, this see here how to add headers with a message inspector.
How do I make my WCF client authenticate using the ACS to my internally hosted WCF service? The issue revolves around setting a custom Realm (which I can't figure out how to set.)
My ACS is configured similar to the ACS Samples however the "Realm" is defined as shown below.
Excerpt from Azure ACS Configuration page
Client Side Code
EndpointAddress serviceEndpointAddress = new EndpointAddress( new Uri( "http://localhost:7000/Service/Default.aspx"),
EndpointIdentity.CreateDnsIdentity( GetServiceCertificateSubjectName() ),
new AddressHeaderCollection() );
ChannelFactory<IStringService> stringServiceFactory = new ChannelFactory<IStringService>(Bindings.CreateServiceBinding("https://agent7.accesscontrol.appfabriclabs.com/v2/wstrust/13/certificate"), serviceEndpointAddress );
// Set the service credentials.
stringServiceFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
stringServiceFactory.Credentials.ServiceCertificate.DefaultCertificate = GetServiceCertificate();
// Set the client credentials.
stringServiceFactory.Credentials.ClientCertificate.Certificate = GetClientCertificateWithPrivateKey();
Server Side Code
string acsCertificateEndpoint = String.Format( "https://{0}.{1}/v2/wstrust/13/certificate", AccessControlNamespace, AccessControlHostName );
ServiceHost rpHost = new ServiceHost( typeof( StringService ) );
rpHost.Credentials.ServiceCertificate.Certificate = GetServiceCertificateWithPrivateKey();
rpHost.AddServiceEndpoint( typeof( IStringService ),
Bindings.CreateServiceBinding( acsCertificateEndpoint ),
"http://localhost:7000/Service/Default.aspx"
);
//
// This must be called after all WCF settings are set on the service host so the
// Windows Identity Foundation token handlers can pick up the relevant settings.
//
ServiceConfiguration serviceConfiguration = new ServiceConfiguration();
serviceConfiguration.CertificateValidationMode = X509CertificateValidationMode.None;
// Accept ACS signing certificate as Issuer.
serviceConfiguration.IssuerNameRegistry = new X509IssuerNameRegistry( GetAcsSigningCertificate().SubjectName.Name );
// Add the SAML 2.0 token handler.
serviceConfiguration.SecurityTokenHandlers.AddOrReplace( new Saml2SecurityTokenHandler() );
// Add the address of this service to the allowed audiences.
serviceConfiguration.SecurityTokenHandlers.Configuration.AudienceRestriction.AllowedAudienceUris.Add( new Uri( "urn:federation:customer:222:agent:11") );
FederatedServiceCredentials.ConfigureServiceHost( rpHost, serviceConfiguration );
return rpHost;
... where urn:federation:customer:222:agent:11 is the Relying party ID
... and http://localhost:7000/Service/Default.aspx is the location I want the above WCF / WIF client to bind to once the ACS authentication is made.
Question
How do I edit the code above so that the client and server will both operate against a certain port (localhost:700) and also with a realm of urn:federation:customer:222:agent:11
I think I have the server code correct; however how do I set AudienceRestriction on the client?
Your server side code looks fine, but Sixto is right about standard channel factories. Luckily, you can request a security token from ACS yourself using a WSTrustChannelFactory. In the context of your sample, your code would look like this:
//
// Get the token from ACS
//
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(
Bindings.CreateAcsCertificateBinding(),
new EndpointAddress( acsCertificateEndpoint ) );
trustChannelFactory.Credentials.ClientCertificate.Certificate = GetClientCertificateWithPrivateKey();
RequestSecurityToken rst = new RequestSecurityToken()
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointAddress( new Uri( "urn:federation:customer:222:agent:11" ) ),
KeyType = KeyTypes.Symmetric
};
WSTrustChannel wsTrustChannel = (WSTrustChannel)trustChannelFactory.CreateChannel();
SecurityToken token = wsTrustChannel.Issue( rst );
//
// Call StringService, authenticating with the retrieved token
//
WS2007FederationHttpBinding binding = new WS2007FederationHttpBinding( WSFederationHttpSecurityMode.Message );
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = false;
ChannelFactory<IStringService> factory = new ChannelFactory<IStringService>(
binding,
new EndpointAddress(
new Uri( ServiceAddress ),
EndpointIdentity.CreateDnsIdentity(GetServiceCertificateSubjectName()) ) );
factory.ConfigureChannelFactory<IStringService>();
factory.Credentials.SupportInteractive = false;
factory.Credentials.ServiceCertificate.DefaultCertificate = GetServiceCertificate();
IStringService channel = factory.CreateChannelWithIssuedToken<IStringService>( token );
string reversedString = channel.Reverse( "string to reverse" );
Some answers may be better late than never. I've been unable to find any official documentation on using WCF in this fashion, however in reading the WS-Trust papers and the MSDN documentation on configuration, I have come up with the following solution which appears to work.
From the service consuming client's config at configuration/system.serviceModel/bindings/ws2007FederationHttpbinding/binding/security/message. It overrides the AppliesTo element of the token request message.
<tokenRequestParameters>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
<Address>urn:x-Organization:Testing</Address>
</EndpointReference>
</wsp:AppliesTo>
</tokenRequestParameters>
Adding this same snippet in the configuration of the service, will cause the Service Reference utility to include this within the trust:SecondaryParameters element of the service client. It must be moved into the parent tokenRequestParameters element to work properly.
Haven't actually tried the approach referenced in this MSDN article but from reading it sounds like the standard channel factory doesn't have the right hooks to do what you want. The WSTrustChannelFactory is built for WIF & SAML but I'm not familiar enough with ACS to determine if it is applicable. This article in this six-part series will probably be worthwhile perusing too.