We currently have a client(MVC .Net Core Web Application) and web API which is used to access Microsoft Graph calls, the authentication process uses Azure AD v2.0 endpoint. we can get this working if we do all of this in the client, however, as soon as we pass the token to the Web API this fails and doesn't even return a response to help us diagnose. below are loads of things we have tried, we have come to the conclusion our azure hasn't been set up correctly but unsure.
Process 1 Works
We forget the web API and do all actions inside the client, this method works but not what we are after so we know the code is not broken.
Process 2 Fails
Client Authenticates to Microsoft graph
redirects with code to the client
send the code to Web API to authenticate, get access token and refresh token
Fails returns nothing
Process 3 Fails
Client Authenticates to Microsoft graph
redirects with code to the client
send the code in the client to authenticate, get access token and refresh token
send access token and refresh token to Web API
use token to perform Microsoft graph action
Fails returns nothing
Azure Settings
Within Microsoft Azure, we have tried configuring the Microsoft Active Directory to follow this example. However, this uses WPF and can't get this to work. We follow it step by step and even the user is prompted with the API permissions be still doesn't work. I am happy to provide any further information to help diagnose this issue.
Our architecture we are trying to achieve.
Update
This is where we exchange the authorization code for an access token however, this gives no return response and times out after about 100 seconds, doesn't even continue.
HttpContent content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"code", code},
{"client_id", _clientId},
{"client_secret", _clientSecret},
{"redirect_uri", _redirectUri},
{"grant_type", _codeGrantType}
});
using (HttpClient client = new HttpClient())
{
var response = await client.PostAsync(_tokenServerUrl, content);
if (response.IsSuccessStatusCode)
{
//get token from body and update expiry time...
var token = await response.Content.ReadAsJsonAsync<OAuthToken>();
token.ExpiresOn = DateTime.Now.AddSeconds(token.ExpiresIn - 100);
return token;
}
Related
I have a SPA application that communicates with my backend Web API using AAD v2 authentication. Now I'm developing a console app to call Microsoft Graph on behalf of the user signed into the SPA app.
I have a valid access token of the user (used to call backend Web API). I want to use this access token to request a new token for accessing MS Graph.
Here is the code of the console app for requesting a new access token with MS Graph scopes using MSAL.NET:
string clientId = "<clientId>";
string clientSecret = "<clientSecret>";
string accessToken = "<validAccessTokenForWebApi>";
string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string[] scopes = new string[] { "User.Read", "Mail.Send" };
string graphAccessToken = null;
try
{
var app = ConfidentialClientApplicationBuilder
.Create(clientId).WithClientSecret(clientSecret).Build();
var userAssertion = new UserAssertion(accessToken, assertionType);
var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion)
.ExecuteAsync().GetAwaiter().GetResult();
graphAccessToken = result.AccessToken;
}
catch (MsalServiceException ex)
{
throw;
}
But when I call app.AcquireTokenOnBehalfOf() I get an exception:
AADSTS50013: Assertion failed signature validation. [Reason - The provided signature value did not match the expected signature value., Thumbprint of key used by client: 'BB839F3453C7C04068B078EDADAB8E6D5F382E76', Found key 'Start=06/04/2019 00:00:00, End=06/04/2021 00:00:00']
What is the reason? What is the right way of getting access token on behalf of a user?
UPDATE - why do I need console app?
I could call Graph API directly from my backend API, but some actions may be delayed by the user (e.g. send mail using Graph API after 30 minutes). That is why I need to do this using the console app that runs on schedule.
If you want to use OAuth 2.0 On-Behalf-Of flow, I think you do not need to develop a console application to call graph api. You can directly use your backend Web API application to acquire access token then call Microsoft Graph. According to my understanding, you just do these steps
Sign-in the user in the client application
Acquire a token to the Web API (TodoListService) and call
it.
The Web API then calls another downstream Web API (The Microsoft
Graph).
For more details, please refer to the sample.
Regarding how to get access token with on behalf flow in the console application, The detailed steps are as below.
Register the web api app
Register APP
Create Client secrets
Configure permissions to access Graph API
Configure an application to expose web APIs(Add scope for the api)
Register the SAP app
Register APP
Create Client secrets
Configure permissions to access web API
Configure known client applications for web API application
In the Azure portal, navigate to your Web api
app registration and click on the Manifest section.
Find the property knownClientApplications and add the Client IDs of the SAP applications
Get access token to call web api
GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?scope=<you web api scope> openid
&redirect_uri=<your sap app redirect url>
&nonce=test123
&client_id=<you sap app client id>
&response_type=id_token token
get access token with on behalf flow
REST API
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&client_id=<you web api client id>
&assertion=<you acess token you get in above steps>
&client_secret=<you app secret>
&scope=https://graph.microsoft.com/user.read
&requested_token_use=on_behalf_of
MSAL.net Code
string[] scopes = { "user.read" };
string accesstoken = "";
string appKey = "yor web api client secret";
string clientId = "your web api application id";
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(appKey)
.Build();
UserAssertion userAssertion = new UserAssertion(accesstoken,
"urn:ietf:params:oauth:grant-type:jwt-bearer");
var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync().Result;
Console.WriteLine(result.AccessToken);
I'm using the below code to get access token and refresh token from docusign.
But I'm always getting the invalid-grant error. I'm pasting the code below.
[HttpGet("GetDocToken")]
[AllowAnonymous]
public async Task<IActionResult> getToken(string docCode)
{
var x = docCode.Length;
var client = new HttpClient();
var authCode=Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("ced8998a-4387-4f30-9ab7-51c0d1af49bf:d7c3ccd4-22fa-4f18-a540-ddf11d8b2c9f"));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authCode);
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
var requestContent = new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", docCode),
new KeyValuePair<string, string>("redirect_uri", "http://localhost:4200/auth")
});
HttpResponseMessage response = await client.PostAsync("https://account-d.docusign.com/oauth/token", requestContent);
string resultContent = response.Content.ReadAsStringAsync().Result;
return Ok(response.Content.ReadAsStringAsync());
}
My assumption is that you have received the authentication code back from the DocuSign identity system and are trying to exchange it for an access token.
A couple of issues:
The docs incorrectly indicates that the redirect_uri should be included in the request. The Example Request in the docs does correctly show that the request should only include the grant_type and code parameters.
Note: While the OAuth standard, section 4.1 (d) does indicate that the redirect_url should be included, DocuSign usually does not include it.
My guess is that DocuSign will ignore the redirect_uri parameter, but you might want to try leaving it out.
Another issue is timing: The authorization code you receive back from DocuSign is only good for a minute or so. If you're not immediately using the authorization code (your code's docCode) then you'll get the Invalid Grant error.
Known good example software
I suggest that you also check out the known-good code example for C#. You can use a protocol peeker to see exactly what it is doing during the authentication.
Use a library
I also suggest that you look for an OAuth Authorization Code client library that you can use instead of rolling your own.
For example, are you setting and checking the state value? It's important to do that to stop CSRF attacks. See this article.
Added
It is also not clear to me that you are using the right value as the authorization code.
I believe the flow should be:
User presses "Authenticate with DocuSign" in the Angular app.
User's browser does a GET to the DocuSign authentication server. At this point, the browser is no longer running the Angular app.
User's browser and DocuSign Authentication server exchange HTML back and forth as the user authenticates with DocuSign.
The user completes the authentication process with DocuSign.
DocuSign sends a REDIRECT response to the browser, telling the browser to do a GET on the redirect url. The redirect (and the GET) include query parameters for code and state
Your SERVER (not the Angular app), receives the GET request.
Your server should:
Extract the code and state query parameters
Verify that state is the same as was sent in step 2.
Makes the POST request to DocuSign to exchange the authorization code for an access token.
RESPONDS to the browser with the Angular program.
The Angular program is now once again running on the browser.
Use Implicit Grant
Your other option is to use Implicit Grant. That way you don't need a server component. With Implicit Grant, your Angular program handles the flow.
I have created a Web API in Azure.
This Web API makes some calls in SharePoint Online. Some of the api calls are on-behalf-of.
This Web API works fine until 01.05.2018 - and it works fine on old app services, which were created before 01.05.2018.
A microsoft staff member said:
As part of our security hardening efforts we do not allow id_token
redemption for any application created after 2018-05-01 00:00:00.
During the log in process of adal, I got the id_token. The id_token has got the same value as the access_token:
When I call the web api, I will send this token as bearer token.
The Web API takes this token (string accessToken) and starts the method 'AcquireTokenAsync':
var clientID = ConfigurationManager.AppSettings["ClientID"];
var clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
var tenant = ConfigurationManager.AppSettings["Tenant"];
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new AuthenticationContext(
"https://login.microsoftonline.com/" + tenant);
var resource = new Uri(sharePointUrl).GetLeftPart(UriPartial.Authority);
var authResult = await authContext.AcquireTokenAsync(resource, appCred,
new UserAssertion(accessToken));
return authResult.AccessToken;
But in the line which calls 'AcquireTokenAsync' I have got the error message:
AADSTS240002: Input id_token cannot be used as 'urn:ietf:params:oauth:grant-type:jwt-bearer' grant
But where is the problem?
The problem is that you use the same application identity in the front-end and back-end, and MS does not allow you to use the Id token (which you use as an access token here because of the former) to get another access token.
A possible solution:
Register another application (the front-end JS app should be a Native app)
It should acquire an access token for your back-end API using either the API's client id or app Id URI as the resource
Then the API can exchange the access token for another access token
If this is a multi-tenant app, the migration is probably not going to be easy.
If it's single-tenant, then all should be possible.
Your front-end app should of course require permission to call your back-end API in Azure AD, and that permission should be granted.
Another solution would be to acquire the other access token in the front-end using ADAL.JS instead of using on-behalf-of in the back-end and attaching that to all API requests in addition to the Id token.
Not sure if this is really silly question, but is it possible to access / utilize the Dropbox within the scope of a web API.
Take the following into consideration. I have a multi-tenant front-end application written in Angular. The back-end (multi-tenant) in a C# web API. The user can upload documents / files. The API will be responsible for uploading the documents to the storage provider. In this case it's Dropbox. The thing is, I have read that Dropbox no longer supports username / password auth. Thus OAuth is used were by the user needs to provide consent to the calling application. This only has to happen once. Since multiple users within a single tenant can utilize the online storage, I'm struggling to see how this would work through a web API...
Any suggestions?
Thanks!
Yes, you can utilize 3rd party API inside Web API. I have never used Drop box API, but as you are saying they use OAuth as authentication mechanism i can show you the code which calls the 3rd party API in this case it is (clarifai).
i am using HttpClientyou can use Dropbox .NET SDK. However if you want to stick to HTTP then here is the documentation for the same.
string token = "Oauth token received using some mechanism";
string requestPath = string.Format("v1/tag");
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://api.clarifai.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
HttpResponseMessage response = await client.PostAsync(requestPath, content);
if (response.IsSuccessStatusCode)
{
var resString = await response.Content.ReadAsStringAsync();
// JSON Response
JObject resJsonObject = JObject.Parse(resString);
}
Now, As you have the concern to authenticate the user and get the token. Here is Dropbox example in which they show how to redirect a user on dropbox website to authenticate and get the temporary oauth token.
Take a look at Connect Action Method and AuthAsync Action Method in Home Controller.
Hope this helps you. If any doubt feel free to ask.
Im using azure mobile service for xamarin forms. I was using non-secureed services it was opened to use. Now client changed the service end point and now end points are secured only authorized user can access the endpoints, so now im logged in with Google account. I have followed the steps:-
Logged in with google account and received the access token.
Pass access token in get request header.
request.Headers["Authorization"] = "Bearer " + accessToken;
Response:-
The remote server returned an error: (401) Unauthorized.
If anybody have any idea please share with me.
You cannot use a Google token to authenticate directly. You swap the google token for a ZUMO token and then add the X-ZUMO-AUTH header with the value of the ZUMO token.
We generally assume you are using one of the client SDKs. However, since you are doing this directly, you will need to construct the request yourself. The endpoint is /login/google and the POST body should be an application/json with the contents {"access_token","your-google-token"}.
Two recommendations: 1) Use the Client SDK (you can over-ride the HttpClient using a delegating handler and a lot of this stuff is taken care of for you). 2) Upgrade to Azure Mobile Apps (Azure Mobile Services is being shut down this year, and all customers are being transitioned to Azure Mobile Apps).
I am not sure if this solves your problem, but in my case following code works (with the bearer token, HttpClient and Xamarin.Forms)
// Set the authorization Header in httpclient
DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "your-access-token");
side-note: I have an own implementation for the API-communication (which just inherits from HttpClient)