Skip to content

Instantly share code, notes, and snippets.

@junalmeida
Created July 19, 2018 22:54
Show Gist options
  • Save junalmeida/2e36ce1cfd578ec77e0567fbd733dc5b to your computer and use it in GitHub Desktop.
Save junalmeida/2e36ce1cfd578ec77e0567fbd733dc5b to your computer and use it in GitHub Desktop.
ADFS Provider to get Saml and Oauth tokens (.netstandard compatible)
using Microsoft.IdentityModel.Tokens.Saml;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace Security
{
class AdfsProvider : IAdfsProvider
{
public AdfsProvider()
{
}
public async Task<JwtSecurityToken> GetBearerTokenByCode(Uri adfs_uri, string code, string client_id, string audience, string redirect_uri, string resource)
{
var post = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>() {
{ "grant_type", "authorization_code" },
{ "client_id", client_id },
{ "code", code },
{ "redirect_uri", redirect_uri },
{ "resource", resource },
});
var response = await post.PostAsync($"{adfs_uri.ToString()}/adfs/oauth2/token", content);
var jwt_str = await response.Content.ReadAsStringAsync();
var obj = JsonConvert.DeserializeObject<IDictionary<string, string>>(jwt_str);
if (obj.ContainsKey("error"))
throw new UnauthorizedAccessException("Unable to authorize the user on this resource.", new ApplicationException("ADFS ERROR: " + jwt_str));
else
{
var h = new JwtSecurityTokenHandler();
return h.ReadJwtToken(obj["access_token"]);
}
}
/// <summary>
/// Get a SamlToken by username and password
/// </summary>
/// <param name="adfs_uri">Full url with protocol</param>
/// <param name="audience"></param>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
public async Task<SamlSecurityToken> GetSamlToken(Uri adfs_uri, string audience, string username, string password)
{
var usernameMixedEndpoint = $"{adfs_uri.ToString()}adfs/services/trust/13/usernamemixed";
var rst = BuildRst(usernameMixedEndpoint, username, password, audience);
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, usernameMixedEndpoint)
{
Content = new StringContent(rst, Encoding.UTF8, "application/soap+xml")
};
var response = await httpClient.SendAsync(request);
try
{
var xDoc = XDocument.Load(new StreamReader(await response.Content.ReadAsStreamAsync()));
var unwrappedResponse = xDoc.Descendants((XNamespace)"http://docs.oasis-open.org/ws-sx/ws-trust/200512" + "RequestedSecurityToken")
.FirstOrDefault()?
.FirstNode as XElement;
if (unwrappedResponse != null)
{
var serializer = new SamlSerializer();
var token = serializer.ReadAssertion(new XmlTextReader(new StringReader(unwrappedResponse.ToString())));
return new SamlSecurityToken(token);
}
else
throw new UnauthorizedAccessException();
}
catch (Exception ex)
{
ex.Data.Add("adfs_uri", adfs_uri.ToString());
ex.Data.Add("audience", audience);
ex.Data.Add("username", username);
var stream = await response.Content.ReadAsStreamAsync();
stream.Seek(0, SeekOrigin.Begin);
ex.Data.Add("adfs response", new StreamReader(stream).ReadToEnd());
throw;
}
}
}
public async Task<SamlSecurityToken> RenewSamlToken(Uri adfs_uri, string audience, string assertionId)
{
var usernameMixedEndpoint = $"{adfs_uri.ToString()}adfs/services/trust/13/usernamemixed";
var rst = BuildRstRenew(usernameMixedEndpoint, audience, assertionId);
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, usernameMixedEndpoint)
{
Content = new StringContent(rst, Encoding.UTF8, "application/soap+xml")
};
var response = await httpClient.SendAsync(request);
try
{
var xDoc = XDocument.Load(new StreamReader(await response.Content.ReadAsStreamAsync()));
var unwrappedResponse = xDoc.Descendants((XNamespace)"http://docs.oasis-open.org/ws-sx/ws-trust/200512" + "RequestedSecurityToken")
.FirstOrDefault()?
.FirstNode as XElement;
if (unwrappedResponse != null)
{
var serializer = new SamlSerializer();
var token = serializer.ReadAssertion(new XmlTextReader(new StringReader(unwrappedResponse.ToString())));
return new SamlSecurityToken(token);
}
else
throw new UnauthorizedAccessException();
}
catch (Exception ex)
{
ex.Data.Add("adfs_uri", adfs_uri.ToString());
ex.Data.Add("audience", audience);
ex.Data.Add("tokenId", assertionId);
var stream = await response.Content.ReadAsStreamAsync();
stream.Seek(0, SeekOrigin.Begin);
ex.Data.Add("adfs response", new StreamReader(stream).ReadToEnd());
throw;
}
}
}
private static string BuildRst(string usernameMixedEndpoint, string userName, string password, string audience)
{
var messageId = Guid.NewGuid();
var tokenId = Guid.NewGuid();
return string.Format(
WsTrustSaml11TokenRstTemplate,
messageId,
usernameMixedEndpoint,
tokenId,
userName,
password,
audience);
}
private static string BuildRstRenew(string endpoint, string audience, string assertionId)
{
var messageId = Guid.NewGuid();
return string.Format(
WsTrustSaml11TokenRstRenewTemplate,
messageId,
endpoint,
audience,
assertionId,
assertionId,
assertionId);
}
const string WsTrustSaml11TokenRstTemplate =
@"<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:{0}</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand=""1"">{1}</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=""uuid-{2}-7"">
<o:Username>{3}</o:Username>
<o:Password o:Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">{4}</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>{5}</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:RequestSecurityToken>
</s:Body>
</s:Envelope>";
const string WsTrustSaml11TokenRstRenewTemplate =
@"<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/Renew</a:Action>
<a:MessageID>urn:uuid:{0}</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand=""1"">{1}</a:To>
</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>{2}</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/Renew</trust:RequestType>
<trust:RenewTarget>
<wsc:SecurityContextToken
xmlns:wsc=""http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512""
xmlns:wsu=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd""
wsu:Id=""uuid:{3}"">
<wsc:Identifier>
uuid:{4}
</wsc:Identifier>
<wsc:Instance>
uuid:{5}
</wsc:Instance>
</wsc:SecurityContextToken>
</trust:RenewTarget>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>";
}
}
using Microsoft.IdentityModel.Tokens.Saml;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Threading.Tasks;
namespace Security
{
public interface IAdfsProvider
{
Task<SamlSecurityToken> GetSamlToken(Uri adfs_uri, string audience, string username, string password);
Task<SamlSecurityToken> RenewSamlToken(Uri adfs_uri, string audience, string assertionId);
Task<JwtSecurityToken> GetBearerTokenByCode(Uri adfs_uri, string code, string client_id, string audience, string redirect_uri, string resource);
}
}
@junalmeida
Copy link
Author

The RenewSamlToken method is not working yet. Help @rafaelcruz-net

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment