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.
Related
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.
I am using LinkedIn oauth 2.0 api implementation and all went well to setup and retrieval of Access Token, but then i revoked the AccessToken after login in LinkedIn and also deleted the AccessToken i saved in my DB.
The idea is to give an option to Re-Authorize the account as given in point 1 below.
Once the request is made, one of the following occurs:
If the member has not previously accepted the application's permission request, or the grant has expired or been manually revoked by the member, the browser is redirected to LinkedIn's authorization screen as shown in the screenshot below. When the member completes the authorization process, the browser is redirected to the URL provided in the redirect_uri query parameter.
If there is a valid existing permission grant from the member, the authorization screen is bypassed and the member is immediately redirected to the URL provided in the redirect_uri query parameter.
What actually happen is that when ever i redirect user to the Authorization URL, it goes for option 2 written above rather than option 1, and with a null Authorization Code. Below given is my Code on click of a button.
var permissions = new string[] { "r_basicprofile", "r_emailaddress", "w_member_social" };
var authUrl = client.GetAuthorizationUrl(permissions,true);
return new RedirectResult(authUrl.ToString());
and below given is the method that get called when LinkedIn callback redirect_uri
public ActionResult LinkedInAuthentication(string code, string state)
{
//the code parameter is null here
}
This should not be the behavior, please help.
Also can i know how to request for RefreshToken?
Ahhh issue resolved, after a lot of search, i started reading complete linkedin documentation and found below given statement under integration > SignIn with LinkedIn rather under Authentication > Authorization Code Flow (3-legged OAuth) which actually explains each step of authentication. The link that explains this is
https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context?trk=eml_mktg_gco_dev_api_comms#authenticating-members
Authenticating Members
New members logging in to your service for the first time will need to follow the Authenticating with OAuth 2.0 Guide. When requesting an authorization code in Step 2 of the OAuth 2.0 Guide, make sure to request the r_liteprofile and/or r_emailaddress scopes!
so changing
var permissions = new string[] { "r_basicprofile", "r_emailaddress", "w_member_social" };
to
var permissions = new string[] { "r_liteprofile", "r_emailaddress"};
resolved the issue.
Hope someone else will be benefited to :)
Also if someone can help how to enable basicprofile and fullprofile features in app? That will be great.
I’m writing a web api that will be called from a background service to fetch some data. After some research I decided to use a Json web token to achieve that but I’m still a bit confused regarding when a new token should be requested.
Let’s say I start up my service, I request a token, the token expires after 15 minutes, then after 20 minutes I make an api call with the expired token. I will get an unauthorized error or something.
My question is: How will the client know when to request a new token? Should it request a new one before every api call? Seems like I’m missing something. Maybe I should make the token permanent and store it in the database?
Thanks
The answer to this is slightly application specific, but the OAuth specification has a mechanism for "refresh tokens", which can be used to grant new "access tokens" (the token typically included on each API request), without having to send the user to the UI authentication process to have them re-authenticate. So, once you request an access token, you will receive a refresh token and an access token. This methodology allows access tokens to be used for much shorter time frames.
This can also be done without refresh tokens, but in those cases the access token timeout would likely be longer, and then you would request that the user re-authenticate through the usual OAuth UI process. Note that even when you do have refresh tokens, the refresh token can also be set to expire, in which would then require a user re-authentication through UI again.
In some API's you just make the API request as usual, and if you get a response that is defined by the API to be one that indicates the access token has expired, you can then issue an API call to refresh the token (or fully request a new one if that is expired, or you the API doesn't have refresh tokens), and then make the original API call again with the new access token.
The API can also have a response that includes the timeout or expiration date/time of the access token as well. Then, the client can avoid sending the initial API call first, and simply send the refresh token call first.
In implementing your API, you could likely use any of these methodologies.
Here's some general discussion on the OAuth spec website, to provide more depth:
https://www.oauth.com/oauth2-servers/making-authenticated-requests/
https://www.oauth.com/oauth2-servers/access-tokens/access-token-lifetime/
https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/
And also, here's an example from the Twitter API regarding response codes showing one of the access token expiration techniques (see the "Error Codes" section, under error code 89, which implies the token has expired and you need to get a new one):
https://developer.twitter.com/en/docs/basics/response-codes
Since your client is background service , you can use the Oauth2 Client Credential Flow . Your background service can request an access token using only its client credentials when the client is requesting access to the protected resources under its control.
With this flow , you does't need to care much about the token expires , if client sends an expired token to web api , web api validate the token and create token expires response to your service , your service check the status code/response , directly send a new token request to web api to get new access token , there is no need to use refresh token which uses in other flows .
The fact is that your harness should be prepared to request any token when getting an Unauthorized status code. What I do in test is to check the expiration datetime, if close enough I refresh or get a new token whatever applies to your Auth. Also when getting an unauthorized status code my code does a refresh once and keep a count. If I get another unauthorized code then I return a false or throw an exception after I log the error on the second try. This works fine for me.
I am trying to renew my external providers OAuth access tokens after they expire. They are stored in my SQL database via the signin manager once the user has logged in using the external provider originally.
The access token is used by the server to make requests on the users behalf (mostly to update my database with some external providers information every now-and-then).
I am using a .NETCore 3 preview-6 Angular starter project with built-in Identity Server (I realize this a preview - but I think my issue is more related to a lack of understanding what I should be doing!). I haven't had too much trouble getting the original external login working (using .AddAuthentication().AddOAuth(...)).
But when my service that is using the provided access token gets a 401 response, I cannot figure how to get my server to renew the access token for the user silently.
I have tried using [Authorize(AuthenticationSchemes = "MyScheme")] on my Controller but this has the external provider responding with a CORS error. I then tried HttpContext.ChallengeAsync(...) in my service, but that just didn't even seem to work at all (maybe this is related to the preview version of .NETCore?).
I have even tried to re-create the scaffolded code that returns a new ChallengeResult(...), but I get the same CORS error when called from my API. (I am unsure what black-magic is going on here to make the request appear like the referrer is the external provider.)
TL;DR (code snippet)
Ideally, I am trying to make the // TODO section work:
// This code is inside a Service I have which is called by a Controller action
var user = await _userManager.GetUserAsync(_httpContextAccessor.HttpContext.User);
var accessToken = await _userManager.GetAuthenticationTokenAsync(user, "MyScheme", "access_token");
_client.SetBearerToken(accessToken);
var response = await _client.GetAsync($"{uri}?{_query.ToString()}");
if (!response.IsSuccessStatusCode) // Just assuming 401 for simplicity
{
// TODO: renew the access token and try again
}
I am expecting to get an updated access token back that I can use to re-do the request. Ideally this would be a silent renew, or a redirect if the requested external access has changed.
Hope this makes sense, please let me know if you need more information.
Thanks
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