ADFS STS authentication with console application
Solution 1
I figured out how to accomplish this. I can't say for certain if this is the best implementation possible, but it works for me.
Class ADFS Token Provider
public class ADFSUsernameMixedTokenProvider
{
private readonly Uri adfsUserNameMixedEndpoint;
/// <summary>
/// Initializes a new instance of the <see cref="ADFSUsernameMixedTokenProvider"/> class
/// </summary>
/// <param name="adfsUserNameMixedEndpoint">i.e. https://adfs.mycompany.com/adfs/services/trust/13/usernamemixed </param>
public ADFSUsernameMixedTokenProvider(Uri adfsUserNameMixedEndpoint)
{
this.adfsUserNameMixedEndpoint = adfsUserNameMixedEndpoint;
}
/// <summary>
/// Requests a security token from the ADFS server
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The password</param>
/// <param name="endpoint">The ADFS endpoint</param>
/// <returns></returns>
public GenericXmlSecurityToken RequestToken(string username, SecureString password, string endpoint)
{
WSTrustChannelFactory factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(adfsUserNameMixedEndpoint));
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = new System.Net.NetworkCredential(string.Empty, password).Password;
RequestSecurityToken token = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(endpoint),
KeyType = KeyTypes.Bearer
};
IWSTrustChannelContract channel = factory.CreateChannel();
return channel.Issue(token) as GenericXmlSecurityToken;
}
}
Class Authentication
public class Authentication
{
private GenericXmlSecurityToken token;
private string site = "https://my.site.com"
private string appliesTo = "http://my.site.com"
private string authUsernameEndpoint = "https://sts-prod.site.com/adfs/services/trust/13/usernamemixed";
public Authentication(PSCredential credential)
{
ADFSUsernameMixedTokenProvider tokenProvider = new ADFSUsernameMixedTokenProvider(new Uri(authUsernameEndpoint));
token = tokenProvider.RequestToken(credential.UserName, credential.Password, appliesTo);
}
public CookieContainer GetFedAuthCookies()
{
string prepareToken = WrapInSoapMessage(token, appliesTo);
string samlServer = site.EndsWith("/") ? site : site + "/";
string stringData = $"wa=wsignin1.0&wresult={HttpUtility.UrlEncode(prepareToken)}&wctx={HttpUtility.UrlEncode("rm=1&id=passive&ru=%2f")}";
CookieContainer cookies = new CookieContainer();
HttpWebRequest request = WebRequest.Create(samlServer) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.CookieContainer = cookies;
request.AllowAutoRedirect = false;
byte[] data = Encoding.UTF8.GetBytes(stringData);
request.ContentLength = data.Length;
using (Stream stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
using (Stream stream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(stream))
{
string responseFromServer = reader.ReadToEnd();
}
}
}
return cookies;
}
private string WrapInSoapMessage(GenericXmlSecurityToken token, string site)
{
string validFrom = token.ValidFrom.ToString("o");
string validTo = token.ValidTo.ToString("o");
string securityToken = token.TokenXml.OuterXml;
string soapTemplate = @"<t:RequestSecurityTokenResponse xmlns:t=""http://schemas.xmlsoap.org/ws/2005/02/trust""><t:Lifetime><wsu:Created xmlns:wsu=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"">{0}</wsu:Created><wsu:Expires xmlns:wsu=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"">{1}</wsu:Expires></t:Lifetime><wsp:AppliesTo xmlns:wsp=""http://schemas.xmlsoap.org/ws/2004/09/policy""><wsa:EndpointReference xmlns:wsa=""http://www.w3.org/2005/08/addressing""><wsa:Address>{2}</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><t:RequestedSecurityToken>{3}</t:RequestedSecurityToken><t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType></t:RequestSecurityTokenResponse>";
return string.Format(soapTemplate, validFrom, validTo, site, securityToken);
}
}
Usage
Authentication auth = new Authentication(credential);
CookieContainer container = auth.GetFedAuthCookies();
HttpWebRequest request = WebRequest.Create("https://api.my.site.com/") as HttpWebRequest;
request.Method = method;
request.ContentType = "application/json";
request.CookieContainer = cookieContainer;
request.AllowAutoRedirect = false;
using (WebResponse response = request.GetResponse())
{
using (Stream dataStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(dataStream))
{
return JsonConvert.DeserializeObject<dynamic>(reader.ReadToEnd());
}
}
}
I use this with a PowerShell cmdlet, which is where the PSCredential object comes from. I hope this helps someone wanting to authenticate with ADFS 3.0 from a C# console application - it took me longer than I would like to admit.
Solution 2
What version of ADFS are you dealing with? Based on version these are the best choices for Web API support
- ADFS 2.0: In this case, the best pattern for web API is to use WS-Trust and WS-* for the interaction with the API over SOAP.
- ADFS 2012R2 (or 3.0): You can use OAuth for this, probably your best bet. We have limited support for building mobile apps using the authorization grant profile. See https://msdn.microsoft.com/en-us/library/dn633593.aspx for additional information with a sample.
- ADFS 2016 (or 4.0): You have the full gamut of OAuth/OpenID Connect supports web API, web app, multi-tier, single page app development patterns. See https://technet.microsoft.com/en-us/windows-server-docs/identity/ad-fs/ad-fs-development for the patterns.
Hope this helps.
Thanks //Sam
(Twitter: @MrADFS)
Comments
-
hsimah almost 2 years
I have a website and API secured with our corporate ADFS-backed token service. I need to hit an endpoint on the API with a C# console application. I am finding a lack of resources for using C# code to access STS secured websites. It uses ADFS 3.0.
When I use an
HttpClient
(or similar) to hit an endpoint I receive an HTML form in return.My code:
Uri baseAddress = new Uri("http://localhost:64022"); using (HttpClient client = new HttpClient() { BaseAddress = baseAddress }) { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "#"); HttpResponseMessage response = client.SendAsync(request).Result; var encoding = ASCIIEncoding.ASCII; using (var reader = new System.IO.StreamReader(response.Content.ReadAsStreamAsync().Result, encoding)) { string responseText = reader.ReadToEnd(); } }
The settings I have in my web.config file for my application are:
<system.identityModel.services> <federationConfiguration> <cookieHandler requireSsl="false" persistentSessionLifetime="1.0:0:0" /> <wsFederation persistentCookiesOnPassiveRedirects="true" passiveRedirectEnabled="true" issuer="https://sts.company.com/adfs/ls/" realm="http://myapp.company.com/" requireHttps="false" /> </federationConfiguration> </system.identityModel.services> <system.identityModel> <identityConfiguration> <audienceUris> <add value="http://myapp.company.com/" /> </audienceUris> <issuerNameRegistry> <trustedIssuers> <add thumbprint="0000000000000000000000000000000000000000" name="https://sts.company.com/adfs/services/trust" /> </trustedIssuers> </issuerNameRegistry> </identityConfiguration> </system.identityModel>
I am not sure what the various terms will be. What will my remote address be? My client id? What is the thumbprint?
-
hsimah over 7 yearsThanks for the reply. Unfortunately the code sample for 3.0 is out of date and I don't know enough to work out which of the new functions I need to use.
-
hsimah over 7 yearsI am using ADFS v3.0. I am not sure what information I need to provide or what endpoint I am supposed to be supplying. I can't find a decent sample of someone doing this from a C# console application. It's all ASP.NET.
-
Imen over 4 yearsactually i dont have the user bame and the password , is there a way use only the adress mail ? or nay other thing ?
-
bagofmilk over 2 yearsSorry to revive this 4 years later... but I'm having trouble figuring out what libraries you used! VS is not really helpful. can you post your "using"s if you still have it?
-
hsimah over 2 years@bagofmilk sorry mate, I wish I could. Been a long time since I worked where I wrote this :(