I have a Postman request sent by a partner/client that requires Oauth2 to hit their web service endpoint. Here is the authorization section:
I need to re-create this request in C#. Should be easy, just click on Code link, and grab the sample code in C# - RestSharp. Two problems:
1- When I execute this in Postman, I get The SAML2 token is not valid because its validity period has ended. So then I click the Get New Access Token button, and I get challenged for credentials:
(With the Client ID and Client Secret, shouldn't I be able to bypass this challenge?)
2- Ultimately I need to be able to run this request in my c# app. When I click the Code link in Postman it gives a nice C# example, but the problem with the sample code generated by Postman is that it assumes the bearer token has already been obtained, and just plops that into the source sample. But the obtaining of the bearer token is a very important piece that Postman omits. Here is my specific example, copied from Postman:
var client = new RestClient("https://myurl-here:7148/foo/ODataV4/WS3_stuff?Company='abc123'");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer ey...massive-string...Zb");
request.AddHeader("Cookie", "ApplicationGatewayAffinity=ab721.more.d6c1a341bc; ApplicationGatewayAffinityCORS=ab..more...bc");
request.AddParameter("application/json", "{a-bunch-of-json-here}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
What are the pieces I'm missing? How can I get the C# equivalent of whatever Postman is doing to obtain the bearer and those Cookie values? And I thought there's a way for applications to achieve all the Oauth handshaking without sending the user to that MS window as in screenshot above - but how is it done?
ClientID and ClientSecret are there to identify your application not the user. They are sent to the Authorization Server so that the server knows that it can issue access tokens to this application. User authentication is another thing. You will always have to authenticate the user in order to get an access token which allows to access that user's data.
Unless you only need to authenticate your application and want to access data which does not belong to any user. In that case you need a client credentials flow, an OAuth flow which allows your application to get an access token.
Postman is a tool for making calls to APIs, it just generates the code which enables you to make the API call. Have a look at some C# OAuth clients (or maybe something for your framework, if you're using one). Those clients will enable you to easily generate new access tokens. Still, if you need a user's access token remember that you will need your users to open a browser. If you're developing a backend app, which does not serve any pages then you can have a look at the OAuth device flow, which enables you to authenticate users on a different device than your app runs.
As for the cookies - make sure whether you really need them. If you're calling an API chances are that those cookies are not required to make the request.
Related
I'm working through the JWT impersonation flows documented here and here. I'm using C#, and though I have worked through a few of the quick start applications, I'm still having some issues.
Existing Flow
The flow I have so far, which seems to be functional in DS sandbox/dev/demo, is:
Send user to DocuSign (oauth/auth). scope is "signature impersonation". (I've tried it with a bunch more permissions thrown in as well.)
After DS auth and impersonation grant, user shows back up on my web app with an authorization code
Take that authorization code and post it to oauth/token to get an access token for my target user
Take that access token and call oauth/userinfo to get the target user's IDs and URL
Create a JWT, sign using shared key pair between my web app and DS, and post it to oauth/token. Receive a 200 response with a seemingly-good-looking token.
This all seems to work correctly so far: all DS calls come back with 200s and data which is shaped as I expect.
Problems
The issue is that I can't actually successfully use that token from the final step to perform further action as the user who my app is impersonating. (I am being sure to use the base_url for the associated user.) When I request a GET from the suggested endpoint (brands), I receive this back:
{
"errorCode": "AUTHORIZATION_INVALID_TOKEN",
"message": "The access token provided is expired, revoked or malformed. Authentication for System Application failed."
}
The response which provided the authorization token includes an expires_in value in the thousands of seconds, and I'm performing all of these requests in serial in my web application. So, expiration or revocation should not be possible at this point. I also haven't touched the token at all, so I would expect it to be well formed.
Here's the code I'm using to post to that endpoint, if it's useful:
private async Task<IgnoreMe> GetBrands(UserInfoAccount account, AccessTokenResponse accessToken)
{
var client = _clientFactory.CreateClient("docusign");
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri($"{account.BaseUri}/restapi/v2.1/accounts/{account.Id}/brands"),
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken!);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
return IgnoreMe.Fail;
}
return IgnoreMe.Succeed;
}
The args to this method are the values which came back from previous API calls: the AccessTokenResponse is from the impersonation call.
I've also tried sending similar requests to several other top-level user/account endpoints, and have received the same or similar errors for all of them.
What am I missing here?
Your flow is a mix if Auth Code Grant and JWT. You are using both.
The token from step 3 should work (But you can omit "impersonation" as it's not required for Auth Code Grant).
The token expires after 8 hours. That may be the reason for your error. You'll need to obtain a new one.
In this particular case, the problem was that I had used the wrong ID for the sub value when constructing the JWT.
Results from the oauth/userinfo endpoint I'm using come back structured as a top-level user ID which is associated with a bucket of accounts. I had used an account ID from one of those buckets rather than the top-level user ID.
I've got all the code working to generate the JWT and I've wired up my ConfigureServices with the proper config code but I'm not seeing the header actually get set.
I assumed that the middleware would do this for you but maybe not, is it up to me to return the token from the login method of my controller and the client to then take it and set the header for subsequent requests?
No it does not.
The way it works is that you send your login credentials to a login server. In most cases its the same but in more secure applications this won't be the case.
The server then authenticates your credentials, creates a JWT token and sends that back to you.
You can then use that JWT in your header when making a request to the application server:
"Authorization":"Bearer xxxxx.yyyyy.zzzzz"
This needs to be done with every call to the server because the point of JWT is that it is stateless, meaning the server does not save the data at all. Instead in reads the JWT token with every call and grants access/functionality based on that.
I have an on-premise Dynamics CRM (2016) that is configured with ADFS (3.0). When a user want's to Login, they get redirected to the ADFS login page and the user enter their Windows AD credentials.
From a .net core application I need to make request to the CRM api using HttpClient. When I try to send the credentials like I normally would for a Windows Auth CRM it doesnt work. I get a 401 Unauthorized. Like below.
HttpClient client = new HttpClient(new HttpClientHandler() { Credentials = new NetworkCredential("myuser", "mypassword", "mydomain") });
var result = client.GetAsync("https://mycrmaddress/api/data/v8.0/accounts");
I also tried using Adal to retrieve a token and attach it as a bearer token to the request but I'm unable to get a token with adal. When I try I receive the following:
The authorization server does not support the requested 'grant_type'. The authorization server only supports 'authorization_code'
ADFS 3.0 doesn't support this flow.
I cannot upgrade to ADFS 4.0 so I would like to know what are my options to make an authenticated call to CRM api (without prompting a login window as this application is a service).
Is there any configuration I can do on ADFS so my first example work? Or is it possible to do it with Adal even if it's ADFS 3.0? Or any other solution...
I found the answer to my question. It's kinda hackish, but I tested it myself and it works. As a temporary solution this will do the trick.
Details are available here: https://community.dynamics.com/crm/f/117/t/255985
ADFS 3.0 supports the Authorization Code flow and this what we will use in this case.
We need to retrieve an authorization code. Normally at this steps a windows is prompted to the user to enter its credentials. By doing a POST and sending the user/password it's possible to retrieve an authorization code.
{authProvider} - ADFS Uri - something like
https://adfs.mycompany.com/adfs/oauth2/
{ClientId} - The Guid used to
by your infrastructure team to add your application to ADFS
{RedirectUri} - The IFD Uri for dynamics - should match the redirect
Url used to by your infrastructure team to add your application to
ADFS
username - The User set up on ADFS and in Dynamics
password - The password for the above user
Then we make the following call with these information using HttpClient.
var uri = $"{authProvider}authorize?response_type=code&client_id={clientId}&resource={redirectUri}&redirect_uri={redirectUri}";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("username",username),
new KeyValuePair<string,string>("password",password),
});
var responseResult = _httpManager.PostAsync(uri, content).Result;
The response content will be an html page (Remember normally this flow prompts a login page to the user). In this page there will be a form that contains the authorization code. using a library like HtmlAgilityPack retrieve the token. This is the hackish part of the solution.
Now that we have an authorization code we need to retrieve an access token.
For that we need to make the following call
var uri = $"{authProvider}token";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","authorization_code"),
new KeyValuePair<string,string>("client_id",clientId),
new KeyValuePair<string,string>("redirect_uri",redirectUri),
new KeyValuePair<string,string>("code",code)
});
var response = await _httpManager.PostAsync(uri, content);
The response content will be a json string that will contain the access token.
With the access token, make the call to CRM rest API.
You will need to attach the token to the HttpClient in the header as a bearer token.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
From now on you can make calls to CRM api and you will be authorized. However be carefull normally access token are short lived. You will either need to increase their lifetime or request a new token everytime it's expired.
Request URL:
POST https://example.com/oauth/token
Request Headers:
Content-Type: application/x-www-form-urlencoded
Request POST Body:
grant_type=password&username=joebloggs&
password=password1234&client_id=myclient&client_secret=myclientsecret
Response:
{
"access_token": "omitted for brevity",
"token_type": "bearer",
"expires_in": 1200,
"refresh_token": "b3cc9c66b86340c5b743f2a7cec9d2f1"
}
I would like to protect my Umbraco web api endpoints using a JWT sent from a single page app. I found this lib to cover most of my needs.
Since I make use of the InMemoryOAuthClientStore the documentation states that a client id and secret must be provided with the authentication request (I presume this is used to match an OAuthClient in the store?):
client_id = A valid client id (Only required if a client store is configured)
client_secret = A valid client secret (Only required if a client store is configured, and the client is "secure")
I would like to know how they are intended to be used. If I make this authorization call to https://example.com/oauth/token using an AJAX call from the client, I need to store the id and secret on the client side. My gut feeling tells me that's not how it's intended to be used.
On the other hand, the client id and secret are accompanied with user credentials in this case, so they're worthless without a valid username and password.
Someone please shed some light on this.
Some clients are capable of keeping some information (client secret) private and some do not. For example, if you make request to oauth endpoint directly from javascript via ajax - this client is not "secure". It's not capable of storing client secret privately, because anyone can see this secret either in javascript code directly or by observing request in browser dev tools. For such clients (mobile applications is another example) it does not make any sense to use client secret in any flow, including "password" flow.
Example of "secure" client is web server you own. So you don't make request to oauth endpoint directly from javascript but instead you send credentials to your server, and then server makes request to oauth endpoint. This client is "secure" in a sense it is capable of privately storing client secret. For such clients it does make sense (and even required I'd say) to require client secret.
So in short, your gut feeling is correct - you should not use client secret in your case, because it does nothing useful.
Now, even if you don't use client secret, you still can use client_id alone. For example you might issue a different set of claims for different clients. Just note that client id is public info, so you should not make any sensitive decisions based on client_id alone - it cannot be trusted.
Also note that "password" flow is the last resort and should only be used by clients you own, and when no other flow is available.
There are 2 main reasons to request a token with a username and password and then storing that token on the requesting end.
Extra security. A username/password combo is not send with every
request but only once every 1200 seconds/minutes.
The server handling the request only needs to check the validity of
the token instead of a performance heavy password hash for every
request.
There are other reasons as well, please see the links below.
https://stormpath.com/blog/token-authentication-scalable-user-mgmt
https://www.w3.org/2001/sw/Europe/events/foaf-galway/papers/fp/token_based_authentication/
https://security.stackexchange.com/questions/63435/why-use-an-authentication-token-instead-of-the-username-password-per-request
https://security.stackexchange.com/questions/151770/token-based-api-security-over-repeated-username-password-requests
What is token based authentication?
Update
The client_id is a public identifier for apps. The client_secret is a secret known only to the application and the authorization server. More info in the following links.
https://security.stackexchange.com/questions/76351/whats-the-point-of-the-client-secret-in-oauth2-if-it-doesnt-need-to-be-used
https://salesforce.stackexchange.com/questions/14009/whats-the-benefit-of-the-client-secret-in-oauth2
https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/
I am writing an app that will talk with Salesforce. Salesforce provides access to APIs via OAuth. I've been attempting to go through the OAuth authentication process described here. Currently, I'm attempting to authorize my app. I have the following code.
// Ask Salesforce for a request token
var request = (HttpWebRequest)(WebRequest.Create(String.Format("https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id={0}&redirect_uri=http://localhost:5004/home/AuthCallback", CONSUMER_KEY)));
request.Method = "POST";
request.ContentType = "application/json";
// Retrieve the request token from the response
var response = (HttpWebResponse)(request.GetResponse());
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string accessCodeData = accessCodeReader.ReadToEnd();
}
This code is triggered when a user clicks a button in my ASP.NET MVC view. When executed, this code calls to Salesforce. I see a request in fiddler. The request header looks like this:
POST /services/oauth2/authorize?response_type=code&client_id={consumerKey}&redirect_uri=http://localhost:5004/home/AuthCallback HTTP/1.1
I am in fact passing my consumer key, I'm just removing it from the example. Regardless, this request returns a 302, with a body size of 0. I might be misunderstanding something. However, I was expecting to get a request token. I was then going to use the request token to get the access token.
What am I doing wrong?
You are misusing the API.
Take a closer look at the sequence diagram at their page (under Obtaining an Access Token): in the auhorization_code flow you are supposed to redirect the browser to their page so that the user sees the login page, provides his/her credentials and you get the token back. Instead, you are trying to POST there using a web request from your server.
This particular flow belongs then to the passive flows group, this group is intended to be used in browser apps, your server redirects the browser to their server and you basically get the response to the uri passed in the redirect_uri parameter and this should point back to your application at your server.
There are other flows, of them one is suited for non-browser apps, it is called resource owner password flow. In this flow it is your application that hosts the login UI and you send the username/password to the authorization server and you get the token back. It is to be read in their docs however whether this flow is supported.
Read more here: http://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified
Take a look how I handle the OAuth2 flow to Google, using the DotNetOpenAuth library. This is a direct solution, applying it to any other provider should be straightforward.
http://www.wiktorzychla.com/2014/11/simple-oauth2-federated-authentication.html