Blockquoteafter access token when I called graph API that returns Authorization_RequestDenied request for the access token
using (var webClient = new WebClient())
{
var requestParameters = new NameValueCollection();
requestParameters.Add("resource", resource);
requestParameters.Add("client_id", clientID);
requestParameters.Add("grant_type", "client_credentials");
requestParameters.Add("client_secret", secret);
var url = $"https://login.microsoftonline.com/{tenant}/oauth2/token";
var responsebytes = await webClient.UploadValuesTaskAsync(url,"POST",requestParameters);
var responsebody =Encoding.UTF8.GetString(responsebytes);
var obj = JsonConvert.DeserializeObject<JObject>(responsebody);
var token = obj["access_token"].Value<string>();
access_token = token;
}
after when i request form get the user list from Azure AD by this way
public async Task<List<listItems>> GetData1( string token)
{
HttpClient http = new HttpClient();
string query = "https://graph.microsoft.com/v1.0/users";
HttpRequestMessage httpClient = new HttpRequestMessage(HttpMethod.Get, query);
httpClient.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var res = await http.SendAsync(httpClient);
var res1= await res.Content.ReadAsStringAsync();
List<listItems> lstUsers = new List<listItems>();
JObject results = JObject.Parse(res1); listItems itm;
foreach (var Jelem in results["value"])
{
string id = (string)Jelem["id"];
string displayName = (string)Jelem["displayName"];
itm = new listItems(); itm.id = id;
itm.displayname = displayName; lstUsers.Add(itm);
}
return lstUsers;
}
than i got "error": { "code": "Authorization_RequestDenied", "message": "Insufficient privileges to complete the operation.", "innerError": { "request-id": "1ba8a3e3-7e27-4bad-affd-6929b9af3a9f", "date": "2019-03-26T10:56:26" } the above error
please help me to solve this error
CAUSE
This problem occurs because the application does not have the required permission to access the user information. So you need to assign necessary privileged for this request.
SOLUTION
To access https://graph.microsoft.com/v1.0/users API One of the following permissions is required.
Permission type (from least to most privileged)
Delegated (work or school account) User.Read, User.ReadWrite,
User.ReadBasic.All,
User.Read.All, User.ReadWrite.All, Directory.Read.All,
Directory.ReadWrite.All,
Directory.AccessAsUser.All
Delegated (personal Microsoft account) User.Read, User.ReadWrite
Application User.Read.All, User.ReadWrite.All, Directory.Read.All,
Directory.ReadWrite.All
See the screen shot below:
AZURE PORTAL WAY OUT
To assign permission on azure portal see the screen shot below:
ASP.NET WEB FORM EXAMPLE:
1. Add New Aspx page To project
Take a new web form, here I have taken as Token.aspx and set its property like below
<%# Page Language="C#" AutoEventWireup="true" Async="true"
CodeBehind="Token.aspx.cs" Inherits="WebFormTest.Token" %>
2. Add New Reference from Nuget
In your project reference add a new service reference from nuget package manager console Like below:
3. Token.aspx.cs
Paste following code outside the scope of Page_Load method You might need to add following reference on your namespace once you encounter missing reference error.
using System.Net.Http;
using System.Net.Http.Headers;
class AccessToken
{
public string access_token { get; set; }
}
// Resource Owner Password Credentials Format
private async Task<string> GetTokenByROPCFormat()
{
string tokenUrl = $"https://login.microsoftonline.com/YourTenantId/oauth2/token";
var req = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = "ApplicationID",
["client_secret"] = "ApplicationSecret",
["resource"] = "https://graph.microsoft.com",
["username"] = "userEmailwithAccessPrivilege",
["password"] = "YourPassword"
});
dynamic json;
dynamic results;
HttpClient client = new HttpClient();
var res = await client.SendAsync(req);
json = await res.Content.ReadAsStringAsync();
//Token Output
results = JsonConvert.DeserializeObject<AccessToken>(json);
Console.WriteLine(results.access_token);
//New Block For Accessing Data from Microsoft Graph API
HttpClient newClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", results.access_token);
HttpResponseMessage response = await newClient.SendAsync(request);
string output = await response.Content.ReadAsStringAsync();
Console.WriteLine("Responsed data Is-\n\n" + output + "");
return output;
}
4. Call GetTokenByROPCFormat() Method inside Page_Load
Now call GetTokenByROPCFormat inside the Page_Load like below
RegisterAsyncTask(new PageAsyncTask(GetTokenByROPCFormat));
5. Token Output
If you set debugger on results variable you would get your token like below
6. Access Microsoft Graph API
Now move to following line and set your debugger like below
string output = await response.Content.ReadAsStringAsync();
You would see following output
Hope it would solve your problem. Thank you.
Related
I'm just trying to change a password of the main account and a sub user in RackSpaceCloud using C# but I keep running into a UserNotAuthorized exception. Its weird because I can do anything else without this error, reset Api keys, list users and userID's(etc.). Sample Code
net.openstack.Core.Domain.CloudIdentity cloudIdentity = new CloudIdentity()//Admin Credits
{
Username = "me",
APIKey = "blahblahblah",
};
CloudIdentityProvider cloudIdentityProvider = new CloudIdentityProvider(cloudIdentity);
cloudIdentityProvider.SetUserPassword("correctUserID", "newP#ssw0rd", cloudIdentity);
And then I error which is confusing because methods like,
cloudIdentityProvider.ListUsers(cloudIdentity)
cloudIdentityProvider.ResetApiKey("UserID", cloudIdentity);
Work Perfectly. Any Help or Ideas would be appreciated.
Oh and Btw the addition info on the exception is always the same. "Unable to authenticate user and retrieve authorized service endpoints"
This is a bug. I have opened issue 528 but in the meantime here is a workaround.
var cloudIdentity = new CloudIdentity
{
Username = "{username}",
APIKey = "{api-key}"
};
var cloudIdentityProvider = new CloudIdentityProvider(cloudIdentity);
var userAccess = cloudIdentityProvider.Authenticate(cloudIdentity);
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("https://identity.api.rackspacecloud.com/v2.0/users/{0}", userAccess.User.Id));
request.Headers.Add("X-Auth-Token", userAccess.Token.Id);
var requestBody = JObject.FromObject(new { user = new { username = userAccess.User.Name } });
((JObject)requestBody["user"]).Add("OS-KSADM:password", "{new-password}");
request.Content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
using (var client = new HttpClient())
{
var response = client.SendAsync(request).Result;
}
The cloud identity used must be an admin if you need to change another user's password, otherwise non-admins may only change their own password.
I am working on a Windows app and am having some issues with cookies. Please note that I am working with Windows.Web.Http, not the System namespace HttpClient.
The API I'm working with uses an auth-header for authentication. Basically after a POST to login, I need a way to get the cookies returned and then use those cookies to perform the subsequent API calls. I posted an example of what I currently have, which succeeds. I can see the cookies in the result object. I'm just not entirely sure where to go from here / how to proceed. Thanks! Any ideas?
using MyApi.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Web.Http;
using Newtonsoft.Json;
using MyApi.Models.Auth;
using MyApi.Models;
namespace MyApi
{
public class MyService
{
private const string MyBaseUrl = "http://api.my.com:3000";
private readonly HttpClient _httpClient = new HttpClient();
public async Task<SignInResponse> AttemptLogin(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
throw new ArgumentException("Username or password is null or empty");
var uri = new Uri(string.Format("{0}/{1}", MyBaseUrl, "auth/signin"));
var authSignIn = new Models.Auth.SignInRequest();
authSignIn.Email = username;
authSignIn.Password = password;
var myObject = JsonConvert.SerializeObject(authSignIn);
// I see the headers in the result object, but I'm not
// sure the best way to a) get them out and b) shove them into
// all of the next calls
var result = await _httpClient.PostAsync(uri,
new HttpStringContent(myObject.ToString(),
Windows.Storage.Streams.UnicodeEncoding.Utf8,
"application/json"));
var content = await result.Content.ReadAsStringAsync();
var successResponse = new SignInResponse();
try
{
successResponse = JsonConvert.DeserializeObject<SignInResponse>(content);
}
catch (Exception)
{
var failResponse = JsonConvert.DeserializeObject<ErrorResponse>(content);
throw new Exception(failResponse.message);
}
return successResponse;
}
}
}
You can use HttpBaseProtocolFilter.CookieManager, e.g.:
var filter = new HttpBaseProtocolFilter();
var cookieManager = filter.CookieManager;
var uri = new Uri("http://api.my.com:3000");
foreach (var cookie in cookieManager.GetCookies(uri))
{
Debug.WriteLine(cookie.Name);
Debug.WriteLine(cookie.Value);
}
Notice, if the cookies are already in the HttpCookieContainer, the cookies will be automatically added in the next requests to http://api.my.com:3000, and no action is required from your side.
If you want to modify them or delete them, the HttpCookieContainer has methods to do that.
Take a look at Flurl. It presents a fluent interface over the Http bits, so you can say something like this to authenticate and reuse the connection with the cookies:
using (var fc = new FlurlClient().EnableCookies())
{
var url = new Url( "http://api.com/endpoint" ) ;
await url
.AppendPathSegment("login")
.WithClient(fc)
.PostUrlEncodedAsync(new { user = "user", pass = "pass" });
var page = await url
.AppendPathSegment("home")
.WithClient(fc)
.GetStringAsync();
// Need to inspect the cookies? FlurlClient exposes them as a dictionary.
var sessionId = fc.Cookies["session_id"].Value;
}
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<string> 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; }
}
In fact, only FedAuth cookie is mandatory when it comes to SharePoint Online/Office 365 authentication.
According to Remote Authentication in SharePoint Online Using Claims-Based Authentication:
The FedAuth cookies enable federated authorization, and the rtFA
cookie enables signing out the user from all SharePoint sites, even if
the sign-out process starts from a non-SharePoint site.
So, it is enough to provide SPOIDCRL HTTP header in order to perform authentication in SharePoint Online/Office 365, for example:
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
var credentials = new SharePointOnlineCredentials(userName,securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
request.Headers.Add(HttpRequestHeader.Cookie, authCookie);
The following examples demonstrates how to perform active authentication in SharePointOnline/Office 365 by providing FedAuth cookie.
Example 1: Retrieve FormDigest via SharePoint 2013 REST API (uisng MsOnlineClaimsHelper class)
public static string GetFormDigest(Uri webUri, string userName, string password)
{
var claimsHelper = new MsOnlineClaimsHelper(webUri, userName, password);
var endpointUri = new Uri(webUri,"/_api/contextinfo");
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
request.Method = WebRequestMethods.Http.Post;
request.Accept = "application/json;odata=verbose";
request.ContentType = "application/json;odata=verbose";
request.ContentLength = 0;
var fedAuthCookie = claimsHelper.CookieContainer.GetCookieHeader(webUri); //FedAuth are getting here
request.Headers.Add(HttpRequestHeader.Cookie, fedAuthCookie); //only FedAuth cookie are provided here
//request.CookieContainer = claimsHelper.CookieContainer;
using (var response = (HttpWebResponse) request.GetResponse())
{
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var content = streamReader.ReadToEnd();
var t = JToken.Parse(content);
return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
}
}
}
Example 2: Retrieve FormDigest via SharePoint 2013 REST API (using SharePointOnlineCredentials class)
public static string GetFormDigest(Uri webUri, string userName, string password)
{
var endpointUri = new Uri(webUri, "/_api/contextinfo");
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
request.Method = WebRequestMethods.Http.Post;
request.Accept = "application/json;odata=verbose";
request.ContentType = "application/json;odata=verbose";
request.ContentLength = 0;
var securePassword = new SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
request.Credentials = new SharePointOnlineCredentials(userName,securePassword);
using (var response = (HttpWebResponse)request.GetResponse())
{
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var content = streamReader.ReadToEnd();
var t = JToken.Parse(content);
return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
}
}
}
Update
The modified version of the example for downloading a file:
public static async Task<byte[]> DownloadFile(Uri webUri,string userName,string password, string relativeFileUrl, CancellationToken ct, TimeSpan? timeout = null)
{
try
{
var securePassword = new SecureString();
foreach (var c in password)
{
securePassword.AppendChar(c);
}
var credentials = new SharePointOnlineCredentials(userName, securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
var fedAuthString = authCookie.TrimStart("SPOIDCRL=".ToCharArray());
var cookieContainer = new CookieContainer();
cookieContainer.Add(webUri, new Cookie("SPOIDCRL", fedAuthString));
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookieContainer;
HttpClient _client = new HttpClient(handler);
if (timeout.HasValue)
_client.Timeout = timeout.Value;
ct.ThrowIfCancellationRequested();
var fileUrl = new Uri(webUri, relativeFileUrl);
var resp = await _client.GetAsync(fileUrl);
var result = await resp.Content.ReadAsByteArrayAsync();
if (!resp.IsSuccessStatusCode)
return null;
return result;
}
catch (Exception) { return null; }
}
I created a github project based on https://stackoverflow.com/users/1375553/vadim-gremyachev 's answer https://github.com/nddipiazza/SharepointOnlineCookieFetcher
with a project that can generate these cookies.
It has releases for Windows, Centos7 and Ubuntu16 and I used mono develop to build it so that it is platform independent.
Intended for users who are not making programs with CSOM in c# but still want to be able to easily get the cookies.
Usage
One Time Only Step: (see Access to the path "/etc/mono/registry" is denied)
sudo mkdir /etc/mono
sudo mkdir /etc/mono/registry
sudo chmod uog+rw /etc/mono/registry
Run program:
Linux: ./SharepointOnlineSecurityUtil -u youruser#yourdomain.com -w https://tenant.sharepoint.com
Windows: SharepointOnlineSecurityUtil.exe -u youruser#yourdomain.com -w https://tenant.sharepoint.com
Enter a password when promped
Result of stdout will have SPOIDCRL cookie.
I still needed both FedAuth and rtFa cookies for my purposes. I tried using just FedAuth, but it wouldn't work without both. Another developer confirmed he saw the same behavior.
NOTE: Legacy authentication must be enabled in your tenant for this to work.
Here is a thread to help obtain both FedAuth and rtFa.
Send Post request to https://login.microsoftonline.com/extSTS.srf with the following body.
Replace UserName, Password, EndPoint Address with relevant values.
<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://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</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>
<o:Username>[username]</o:Username>
<o:Password>[password]</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>[endpoint]</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
</t:RequestSecurityToken>
</s:Body>
</s:Envelope>
Note the content of the wsse:BinarySecurityToken node within the response data.
Send Post request to https://YourDomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0.
Replace 'YourDomain with relevant value. Provide the wsse:BinarySecurityToken content within the body of the request.
The response header will contain the FedAuth and rtFa cookies.
This is what I did. Might be useful to future readers!
For my usecase, I am running a WinForms Windows client side app. I was able to grab the FedAuth and rtFA cookie using a embedded WebBrowser Control.
I have uploaded my sample test project to github here: https://github.com/OceanAirdrop/SharePointOnlineGetFedAuthAndRtfaCookie
Here is what I did:
Step 01: Naviagte to SharePoint Url in WebBrowser Control
Using the WebBrowser control, first navigate to a web page on your sharepoint site that you have access to. It can be any page. The aim here is to get the cookies from the loaded page. This step only needs to be done once in the app.
webBrowser1.Navigate(#"https://xx.sharepoint.com/sites/xx/Forms/AllItems.aspx");
Step 02: Grab Cookies from WebBrowser Control
Next, override the Navigated event in the WebBrowser control. This lets you know the page has fully loaded.
Now, heres the wrinkle!! The FedAuth cookies are written with an HTTPOnly flag, which means they cannot be accessed from the .NET Framework. This means if you try to access the cookies of the WebBrowser control, you will get null string back!
// This line of code wont work and will return null
var cookies = webBrowser1.Document.Cookie;
So, to get around this, you instead need to call InternetGetCookieEx in the WININET.dll. I took the code from here. This is what the Navigated function handler looks like:
private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
try
{
if (webBrowser1.Url.AbsoluteUri == "about:blank")
return;
// This line calls through to InternetGetCookieEx
var cookieData = GetWebBrowserCookie.GetCookieInternal(webBrowser1.Url, false);
if (string.IsNullOrEmpty(cookieData) == false)
{
textBoxCookie.Text = cookieData;
var dict = ParseCookieData(cookieData);
textBoxFedAuth.Text = dict["FedAuth"];
textBoxrtFa.Text = dict["rtFa"];
}
}
catch (Exception)
{
}
}
Step 03: Progrmatically Make WebRequest Using Cookies
Now that we have the FedAuth and rtFA cookies we can continue on and use the HttpClient to call any andpoint we need. In my case calling many endpoints that contain images. The code looks like this:
private void buttonDownloadImage_Click(object sender, EventArgs e)
{
try
{
var url = $"https://xx.sharepoint.com/sites/xx/xx/Images/{textBoxImageName.Text}";
var handler = new HttpClientHandler();
handler.CookieContainer = new System.Net.CookieContainer();
// Add our cookies to collection
var cc = new CookieCollection();
cc.Add(new Cookie("FedAuth", textBoxFedAuth.Text));
cc.Add(new Cookie("rtFa", textBoxrtFa.Text));
handler.CookieContainer.Add(new Uri(url), cc);
var httpClient = new HttpClient(handler);
var resp = httpClient.GetAsync(url).Result;
var byteData = resp.Content.ReadAsByteArrayAsync().Result;
if (resp.IsSuccessStatusCode)
{
pictureBox1.Image = byteArrayToImage(byteData);
}
}
catch (Exception)
{
}
}
Thats it. And it works like a charm.
I am new to DotNetOpenAuth and I can't find what value to use as the verifier in ProcessUserAuthorization.
What I want to achieve is to log in with my user credentials into an application (called UserVoice) that uses OAuth. Here's what my code looks like:
string requestToken;
var authorizeUri = consumer.RequestUserAuthorization(new Dictionary<string, string>(), null, out requestToken).AbsoluteUri;
var verifier = "???";
var accessToken = consumer.ProcessUserAuthorization(requestToken, verifier).AccessToken;
consumer.PrepareAuthorizedRequest(endpoint, accessToken, data).GetResponse();
I tried to use my username, my password, my consumer key, my consumer secret, but nothing seems to work. Does someone know which value I should use as the verifier?
Thanks
I finally found a way to log in to UserVoice with DotNetOpenAuth. I think UserVoice's implementation of OAuth wasn't standard, but I was able to do it during this:
var consumer = new DesktopConsumer(this.GetInitialServiceDescription(), this._manager)
string requestToken;
consumer.RequestUserAuthorization(null, null, out requestToken);
// get authentication token
var extraParameters = new Dictionary<string, string>
{
{ "email", this._email },
{ "password", this._password },
{ "request_token", requestToken },
};
consumer = new DesktopConsumer(this.GetSecondaryServiceDescription(), this._manager);
consumer.RequestUserAuthorization(extraParameters, null, out requestToken);
Where GetInitialServiceDescription returns the good request description, and GetSecondaryServiceDescription is a hacked version and returns the authorize endpoint in place of the request token endpoint. The "request_token" returned this way (which is not really a normal request_token from my understanding of OAuth) can then be used as an access token for PrepareAuthorizedRequest.
The verifier is the code that UserVoice would display onscreen after the user has said they want to authorize your app. The user must copy and paste this verifier code from the web site back into your application's GUI, so that it can then pass it into the ProcessUserAuthorization method.
This is only required in OAuth 1.0a (not 1.0), and is there to mitigate certain exploitable attacks that were discovered in 1.0. In your ServiceProviderDescription be sure you specify that the service is a 1.0a version (if in fact Uservoice supports that) so that DNOA will communicate to Uservoice that it should create a verifier code.
Incidentally, various tricks including scanning process titles or hosting the browser within your own app can eliminate the manual user copying the verify code step by having your app automatically copy it for him.
The verifier is also used when the Authorization is done via WebAPI and you do not have a redirect displayed in a browser. In this you just send your AuthentificationRequest via code and get the verifier as a json-string without any user interaction.
In this case the process (for OAuth 1.0) looks as follows:
public void AccessAPI ()
{
InMemoryOAuthTokenManager tokenManager = InMemoryOAuthTokenManager(YOUR_CLIENT_KEY, YOUR_CLIENT_SECRET);
var consumer = new DesktopConsumer(GetAuthServerDescription(), tokenManager);
// Get Request token
string requestToken;
var parameters = new Dictionary<string, string>();
parameters["email"] = "foo";
parameters["password"] = "bar";
Uri authorizationUrl = consumer.RequestUserAuthorization(null, parameters, out requestToken);
// Authorize and get a verifier (No OAuth Header necessary for the API I wanted to access)
var request = WebRequest.Create(authorizationUrl) as HttpWebRequest;
request.Method = "Get";
request.Accept = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
var response = request.GetResponse() as HttpWebResponse;
string verifier = new StreamReader(response.GetResponseStream()).ReadToEnd().Split('=')[1]; //Irgendwie will Json nicht parsen
// Use verifier to get the final AccessToken
AuthorizedTokenResponse authorizationResponse = consumer.ProcessUserAuthorization(requestToken, verifier);
string accessToken = authorizationResponse.AccessToken;
// Access Ressources
HttpDeliveryMethods resourceHttpMethod = HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest;
var resourceEndpoint = new MessageReceivingEndpoint("https://api.discovergy.com/public/v1/meters", resourceHttpMethod);
using (IncomingWebResponse resourceResponse = consumer.PrepareAuthorizedRequestAndSend(resourceEndpoint, accessToken))
{
string result = resourceResponse.GetResponseReader().ReadToEnd();
dynamic content = JObject.Parse(result);
}
}
private ServiceProviderDescription GetAuthServerDescription()
{
var authServerDescription = new ServiceProviderDescription();
authServerDescription.RequestTokenEndpoint = new MessageReceivingEndpoint(YOUR_REQUEST_ENDPOINT, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
authServerDescription.UserAuthorizationEndpoint = new MessageReceivingEndpoint(YOUR_AUTHORIZATION_ENDPOINT, HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
authServerDescription.AccessTokenEndpoint = new MessageReceivingEndpoint(YOUR_TOKEN_ENDPOINT, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
authServerDescription.ProtocolVersion = ProtocolVersion.V10;
authServerDescription.TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() };
return authServerDescription;
}
I am creating an app to get information from Fitbit.com using OAuth.
protected void btnConnect_Click(object sender, EventArgs e)
{
// Create OAuthService object, containing oauth consumer configuration
OAuthService service = OAuthService.Create(
new EndPoint(RequestTokenUrl, "POST"), // requestTokenEndPoint
new Uri(AuthorizationUrl), // authorizationUri
new EndPoint(AccessTokenUrl, "POST"), // accessTokenEndPoint
true, // useAuthorizationHeader
"http://app.fitbit.com", // realm
"HMAC-SHA1", // signatureMethod
"1.0", // oauthVersion
new OAuthConsumer(ConsumerKey, ConsumerSecret) // consumer
);
try
{
var personRepository = new PersonRepository();
var person = personRepository.GetPersonById(int.Parse(personSelect.SelectedItem.Value));
OAuthRequest request = OAuthRequest.Create(
new EndPoint(ProfileUrl, "GET"),
service,
this.Context.Request.Url,
//this.Context.Session.SessionID);
person.FitbitAuthAccessToken,
);
request.VerificationHandler = AspNetOAuthRequest.HandleVerification;
OAuthResponse response = request.GetResource();
// Check if OAuthResponse object has protected resource
if (!response.HasProtectedResource)
{
var token = new OAuthToken(TokenType.Request, person.FitbitAuthAccessToken,
person.FitbitAuthSecret, ConsumerKey);
// If not we are not authorized yet, build authorization URL and redirect to it
string authorizationUrl = service.BuildAuthorizationUrl(response.Token).AbsoluteUri;
Response.Redirect(authorizationUrl);
}
person.FitbitAuthAccessToken = response.Token.Token;
person.FitbitAuthSecret = response.Token.Secret;
person.PersonEncodedId = Doc["result"]["user"]["encodedId"].InnerText;
personRepository.Update(person);
// Store the access token in session variable
Session["access_token"] = response.Token;
}
catch (WebException ex)
{
Response.Write(ex.Message);
Response.Close();
}
catch (OAuthRequestException ex)
{
Response.Write(ex.Message);
Response.Close();
}
}
I save Fitbit Access Token and Secret in database.
How can I get information using just Access token and secret, without authorizing every time?
This would assume that the FitBit api was robust enough to not quire authentication every single time. I have seen API's implementing OAuth where you have an authentication process, then from there most of your calls simply require the AccessToken or secret. I would look at the method signatures for the service and see what types of parameters they are requiring.
If you look at the FitBit API about authentication and accessing resources, you will see that you just need to request the data you are interested in and add in the oAuth header with the access token. Here is what it should look like (from the API page):
GET /1/user/-/activities/date/2010-04-02.json HTTP/1.1
Host: api.fitbit.com
Authorization: OAuth realm="api.fitbit.com",
oauth_consumer_key="fitbit-example-client-application",
oauth_token="8d3221fb072f31b5ef1b3bcfc5d8a27a",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1270248088",
oauth_nonce="515379974",
oauth_signature="Gf5NUq1Pvg3DrtxHJyVaMXq4Foo%3D"
oauth_version="1.0"`
The base signature string will look like:
GET&http%3A%2F%2Fapi.fitbit.com%2F1%2Fuser%2F-%2Factivities%2Fdate%2F2010-04-02.json&oauth_consumer_key%3Dfitbit-example-client-application%26oauth_nonce%3D515379974%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1270248088%26oauth_token%3D8d3221fb072f31b5ef1b3bcfc5d8a27a%26oauth_version%3D1.0
I figured I'd offer my VerifyAuthenticationCore that is part of my FitbitClient that inherits from OAuthClient. It took me a while to get this working but I found that I was missing HttpDeliveryMethods.AuthorizationHeaderRequest when I was creating the web request. Adding this allowed the call to stop returning bad request (400) error messages.
The code below is basically using the user id and the access token to get the user profile information. All calls should basically work this way. All you would need to do is change the url and provide the id and token.
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
string username;
var accessToken = response.AccessToken;
var userId = response.ExtraData["encoded_user_id"];
var httpWebRequest = WebWorker.PrepareAuthorizedRequest(new MessageReceivingEndpoint(new Uri("http://api.fitbit.com/1/user/" + userId + "/profile.json"), HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), accessToken);
var dictionary = new Dictionary<string, string>();
dictionary.Add("accesstoken", accessToken);
dictionary.Add("link", "http://www.fitbit.com/user/" + userId);
using (var webResponse = httpWebRequest.GetResponse())
{
using (var stream = webResponse.GetResponseStream())
using (var reader = new StreamReader(stream))
{
var profile = JObject.Parse(reader.ReadToEnd())["user"];
dictionary.AddItemIfNotEmpty("name", profile["displayName"]);
dictionary.AddItemIfNotEmpty("pictureUrl", profile["avatar"]);
username = dictionary["name"];
}
}
return new AuthenticationResult(true, ProviderName, userId, username, dictionary);
}