Acccess Token (ADAL/Office 365 API)
In 2014 we have seen Microsoft invest a lot in what they called the Office 365 API tools. These tools use the REST (_api) endpoint of SharePoint Online and gets authenticated by the access token in a header. The access token is coming from an Azure Active Directory Application that is registered to allow delegation of access to SharePoint. The level of access to SharePoint is always equal to the access level of the user executing the call.
Right now the functionality of the Office 365 API client libraries are rather limited (Microsoft.Office365.SharePoint) so if you want to do anything other than the most simple things you have to build the REST request yourself. That can be annoying and also as mentioned not everything can be accomplished by the REST endpoint. So if you want you are actually also able to use the acquired access token to authenticate with the CSOM, in that way the full client side functionality is available to use.
If you are interested in the Access Token approach, I encourage you to read Steve Peschka’s blog post on the topic.
One final note, when working with SharePoint apps, you are also using the Access Token approach. Hope this article helps clear up a few things.
The scenario is the following: I need to perform a federated authentication of a user (which uses his university account) into the Sharepoint site of his university and to
obtain both the FedAuth and rtFa cookies (which I have to pass to SharePoint REST webservices in order to access resources).
I made some attempts but there is at least an issue in each one:
1) Using Microsoft.SharePoint.Client library
ClientContext context = new ClientContext(host);
SharePointOnlineCredentials creds = new SharePointOnlineCredentials(user, passw);
context.Credentials = creds;
Uri sharepointuri = new Uri(host);
string authCookie = creds.GetAuthenticationCookie(sharepointuri);
Web web = context.Web;
context.Load(web, w=>w.Lists);
context.ExecuteQuery();
fedAuthString = authCookie.Replace("SPOIDCRL=", string.Empty);
This way I manage to get the FedAuth cookie but
I am unable to get the rtFa cookie.
How can I get the rtFa cookie at this point? Can I intercept the HTTP request involved in such an operation (i.e., context.ExecuteQuery()) -- which presumably contains the rtFa cookie in the headers? Or, can I get the rtFa cookie by only leveraging on the FedAuth cookie?
2) Using MsOnlineClaimsHelper
This is a helper class which can be found on the Internet (e.g., here
http://blog.kloud.com.au/tag/msonlineclaimshelper/ ).
This class, as it is, works with normal authentication but
fails with federated authentication.
So I adjusted it in order to make it work in this case. As long as I understand, the steps are the following:
- Authenticate using username and password to the STS ADFS service of the university (the "federated party" or the ISSUER) -- here the Relying Party is Sharepoint O365 STS ("https://login.microsoftonline.com/extSTS.srf")
- If the auth succeeds, I get back a SAML assertion containing the claims and a security token
- Now, I authenticate to the SharePoint site by passing the Security Token
- If the token is recognized, I get back a response which contains the two cookies (FedAuth and rtFa)
I am not an expert in this matter, and I came out with the following code:
This is the code that calls the method above and try to get FedAuth and rtFa from credentials in two steps (step 1: get SAML token from Federated Party; step 2: pass token from Federated Party to Sharepoint):
private List GetCookies(){
// 1: GET SAML XML FROM FEDERATED PARTY THE USER BELONGS TO
string samlToken = getResponse_Federation(sts: "https://sts.FEDERATEDDOMAIN.com/adfs/services/trust/13/usernamemixed/",
realm: "https://login.microsoftonline.com/extSTS.srf");
// 2: PARSE THE SAML ASSERTION INTO A TOKEN
var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers;
SecurityToken token = handlers.ReadToken(new XmlTextReader(new StringReader(samlToken )));
// 3: REQUEST A NEW TOKEN BASED ON THE ISSUED TOKEN
GenericXmlSecurityToken secToken = GetO365BinaryTokenFromToken(token);
// 4: NOW, EASY: I PARSE THE TOKEN AND EXTRACT FEDAUTH and RTFA
...............
}
private string getResponse_Federation(string stsUrl, string relyingPartyAddress)
{
var binding = new Microsoft.IdentityModel.Protocols.WSTrust.Bindings.UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);
binding.ClientCredentialType = HttpClientCredentialType.None;
var factory = new WSTrustChannelFactory(binding, stsUrl);
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "password";
factory.Credentials.SupportInteractive = false;
factory.TrustVersion = TrustVersion.WSTrust13;
IWSTrustChannelContract channel = null;
try
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(relyingPartyAddress), //("urn:sharepoint:MYFEDERATEDPARTY"),
ReplyTo = relyingPartyAddress,
KeyType = WSTrust13Constants.KeyTypes.Bearer,
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
RequestDisplayToken = true,
};
channel = (WSTrustChannel)factory.CreateChannel();
RequestSecurityTokenResponse response = null;
SecurityToken st = channel.Issue(rst, out response);
var genericToken = st as GenericXmlSecurityToken;
return genericToken.TokenXml.OuterXml;
}
catch (Exception e)
{
return null;
}
}
private GenericXmlSecurityToken GetO365BinaryTokenFromToken(SecurityToken issuedToken)
{
Uri u = new Uri("https://login.microsoftonline.com/extSTS.srf");
WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.IssuedToken;
Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory channel =
new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
binding, new EndpointAddress("https://login.microsoftonline.com/extSTS.srf"));
channel.TrustVersion = TrustVersion.WSTrust13;
channel.Credentials.SupportInteractive = false;
GenericXmlSecurityToken token = null;
try
{
RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer)
{
};
rst.AppliesTo = new EndpointAddress("urn:sharepoint:MYFEDERATEDPARTY");
channel.ConfigureChannelFactory();
var chan = (Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel)channel.CreateChannelWithIssuedToken(issuedToken);
RequestSecurityTokenResponse rstr = null;
token = chan.Issue(rst, out rstr) as GenericXmlSecurityToken;
return token;
}
catch (Exception ex){
Trace.TraceWarning("WebException in getO365BinaryTokenFromADFS: " + ex.ToString());
throw;
}
}
I managed to get back a SAML token from the university STS. However, when parsed, the resulting SecurityToken has no security keys (i.e., the SecurityKeys collection is empty)
With no keys, I get on GetO365BinaryTokenFromToken() but when I try to send the token to the SharePoint Authentication service -- I get the following error: "The signing token Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken has no keys. The security token is used in a context that requires it to perform cryptographic operations, but the token contains no cryptographic keys. Either the token type does not support cryptographic operations, or the particular token instance does not contain cryptographic keys. Check your configuration to ensure that cryptographically disabled token types (for example, UserNameSecurityToken) are not specified in a context that requires cryptographic operations (for example, an endorsing supporting token)."
I think that there are also some configuration issues that I cannot control directly, on both sides (the university STS ADFS and the Sharepoint STS).
I hope that more expert people would bring clarity in this process and even provide advice to actually make this scenario work.
File download function
With the following function, I am able to download a file (given an URL such as
https://myfederatedparty.sharepoint.com/sites/MYSITE/path/myfile.pdf) by issuing
BOTH the FedAuth and the rtFa cookie. If I do not pass the rtFa cookie, I get an "Unauthorized" response.
public static async Task<byte[]> TryRawWsCall(String url, string fedauth, string rtfa, CancellationToken ct, TimeSpan? timeout = null) {
try {
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = new System.Net.CookieContainer();
CookieCollection cc = new CookieCollection();
cc.Add(new Cookie("FedAuth", fedauth));
cc.Add(new Cookie("rtFa", rtfa));
handler.CookieContainer.Add(new Uri(url), cc);
HttpClient _client = new HttpClient(handler);
if (timeout.HasValue)
_client.Timeout = timeout.Value;
ct.ThrowIfCancellationRequested();
var resp = await _client.GetAsync(url);
var result = await resp.Content.ReadAsByteArrayAsync();
if (!resp.IsSuccessStatusCode)
return null;
return result;
}
catch (Exception) { return null; }
}