Actors:
Black box (MS Power App custom connector) with user logged in
API A - .NET Standard WebApi with [Authorize] attribute and AAD authorization enabled (no additional verification in code), ida:audience="A" in web.config
API B - .NET Standard WebApi with [Authorize] attribute and AAD authorization enabled (no additional verification in code), ida:audience="B" in web.config
API C - .NET Standard WebApi with [Authorize] attribute and AAD authorization enabled (no additional verification in code), ida:audience="C" in web.config
Flow:
Power App connector calls API A. Within the code of API A (C#) I can decode JWT from Authorization header and see which user is logged in. Now, I have to call API B and API C and then return something to Power App connector. API B and API C must be able to also decode JWT and get UPN from token.
Problem:
When I tried to reuse whole Authorization header (just get it within API A and add to HttpClient to call API B and C) I get 401.
Now I know that was because of ida:Audience value. I suppose it is part of .NET framework verification, beyond my control. When I changed ida:Audience in API B and C to "A" it worked. But it was only for testing purposes, I am not allowed to do it in production.
Then I tried to set scopes in Power App connector to multiple values (delimited by space) with different audiences - no luck, error from AAD, multiple audiences not allowed.
So, my question is: is it possible in my scenario to do what I need? I have only access token for audience A and have to authorize (with impersonation) in endpoints with other audience value.
This is exactly what the on-behalf-of flow is for.
It allows API A to take the access token it received and ask for an access token to API B (with the same user info in the token).
More info on the flow: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.
Example request content from documentation to token endpoint that uses a client secret:
//line breaks for legibility only
POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com/<tenant>
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&client_secret=sampleCredentia1s
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCJ9.eyJhdWQiOiIyO{a lot of characters here}
&scope=https://graph.microsoft.com/user.read+offline_access
&requested_token_use=on_behalf_of
Using MSAL.NET is definitely better than using the HTTP endpoint directly though.
You would replace the scope in this request with a scope from API B.
I am following a tutorial from this URL
If you see the DeviceCodeAuthProvider and GetAccessToken, you would be able to get the token merely just by the app Id and the scope without any app/ client secret or password. You can also refresh the token just by providing the same info. My question is, is this safe? I thought the client id is just like a username.
Please refer the official docs. The DeviceCodeflow will provide the device code to validate the user/client id/app id and that is safe.
Device code flow protocol:
I have enabled Azure App services authentication and registered my function in AAD, and the client then uses OAUTH2 to get a token and then call the API using that token and this works fine.
But I am struggling to find the documentation about how I based on that token can get the Client Application name that the client used to authenticate in my code
Let’s say my function app is testauth and the client app for the mobile app is clienttest
They use the clientID and clientSecret from the clienttest to auth to the resourceId of testauth
So now in my C# code I want to know the name clienttest is that possible and if so how?
I read about claims but I cannot really put the puzzle together
You can get the clientID of clienttest. If you manage a map of the client name to clientID in your function app, then you will be able to know the client name.
1. You can get the token from http header. Here is the official documentation: Retrieve tokens in app code
AS you are using AAD as the provider, so you may get the access token from http header: X-MS-TOKEN-AAD-ACCESS-TOKEN
2. Then you can decode the token. It is a JWT, which contains 3 parts: header, payload and signature.
All of them are base64url encoded, and are concatenated using periods. So, you can either use popular JWT libraries to decode it or just base64url decode the payload part. Then you will get a JSON string. Finally, you can get the appid claim:
With the appid claim, you will know the client name.
Update
You can get app name you registered in Azure AD with Graph API
With $filter expression, you can get app details. Here is a sample request: https://graph.microsoft.com/v1.0//applications?$filter=appId eq '68a1189c-a6b0-48fe-908c-841916d72731'
And you will get the following response:
You can see that the app's display name is included.
Scenario
I have an Exchange Online environment and service/daemin (no interactive user) application on the Azure VM. Service uses EWS managed API to work with emails in the mailbox of any tenant user. Now EWS client uses Basic authentication that, according to Microsoft, will become unsupported in EWS to access Exchange Online.
Question/Issue
So, I need to find a way to get valid access token for service/daemon application to use with EWS managed API.
My findings
The following article shows an example of using OAuth 2.0 with EWS managed API. This example works, but it uses interactive method of getting consent (sign-in form appears allowing user authenticate themselves and grant requested permission to application) that is not suitable for service/daemon app scenario, because there is no interactive user.
For service/daemon application I need to use client credential authentication flow.
Registered application
Using admin account on https://aad.portal.azure.com portal I registered application with Azure Active Directory. Added client secret for registered application.
Aforementioned article uses https://outlook.office.com/EWS.AccessAsUser.All as a scope. But I did not find permission with such a URL on the portal. I found only the following permissions under Office 365 Exchange Online > Application permissions > Mail:
https://outlook.office365.com/Mail.Read Allows the app to read mail in all mailboxes without a signed-in user
https://outlook.office365.com/Mail.ReadWrite Allows the app to create, read, update, and delete mail in all mailboxes without a signed-in user.
I added both of them and granted admin consent for all users.
Getting access token
For testing purposes and simplicity I did not use any auth libraries (ADAL, MSAL etc.). I used Postman to get access token, then set token variable in debug (see code snippet later in the post).
I tried different endpoints to get acess token.
OAuth 2.0 token endpoint (v2)
POST: https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token
grant_type=client_credentials
client_id=***
client_secret=***
scope=https://outlook.office.com/EWS.AccessAsUser.All
Sending this request produces the following error response:
AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope https://outlook.office.com/EWS.AccessAsUser.All is not valid.
I tried changing scope to https://outlook.office.com/.default. Access token was returned, but it appeared to be invalid for EWS. EWS client throws 401 error with the following value of x-ms-diagnostics response header:
2000008;reason="The token contains no permissions, or permissions can not be understood.";error_category="invalid_grant"
OAuth 2.0 token endpoint (v1)
POST: https://login.microsoftonline.com/<TENANT_ID>/oauth2/token
grant_type=client_credentials
client_id=***
client_secret=***
resource=https://outlook.office.com
Access token was returned, but also appeared to be invalid for EWS. EWS client throws 401 error with the same value of x-ms-diagnostics response header as described ealier in #1.
Use aquired access token with EWS managed API
Here is code sample that I used to test EWS client with access token acquired in Postman:
var token = "...";
var client = new ExchangeService
{
Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
Credentials = new OAuthCredentials(token),
ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress,
"user#domain.onmicrosoft.com"),
};
var folder = Folder.Bind(client, WellKnownFolderName.SentItems);
We had a similar problem: We wanted to use a Service Account to connect to a single mailbox and just doing some stuff with the EWS API (e.g. searching in the GAL) and the full_access_as_app seems like an overkill.
Fortunately it is possible:
Follow the normal "delegate" steps
And use this to get a token via username/password:
...
var cred = new NetworkCredential("UserName", "Password");
var authResult = await pca.AcquireTokenByUsernamePassword(new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" }, cred.UserName, cred.SecurePassword).ExecuteAsync();
...
To make this work you need to enable the "Treat application as public client" under "Authentication" > "Advanced settings" because this uses the "Resource owner password credential flow". (This SO answer helped me alot!)
With that setup we could use a "tradional" username/password way, but using OAuth and the EWS API.
You can protect your client application with either a certificate or a secret. The two permissions that I needed to get this to work were Calendars.ReadWrite.All and full_access_as_app. I never tried acquiring my token via PostMan, but use AcquireTokenAsync in Microsoft.IdentityModel.Clients.ActiveDirectory. In that call, the resource parameter I use is https://outlook.office365.com/. It's pretty simple once you know all the little twists and turns. And full disclosure: I was one lost puppy until MSFT support helped me through this. The doc on the web is often outdated, conflicting, or at best, confusing.
You need to register your app in Azure and use certificate based authentication. https://blogs.msdn.microsoft.com/emeamsgdev/2018/09/11/authenticating-against-exchange-web-services-using-certificate-based-oauth2-tokens/
I run into the same issue while following Microsoft official docs for OAuth 2.0 client credentials flow
According to the Microsoft identity platform and the OAuth 2.0 client credentials flow, the scope "should be the resource identifier (application ID URI) of the resource you want, affixed with the .default suffix" (see default scope doc).
So the question is how to convert https://outlook.office.com/EWS.AccessAsUser.All into the resource identifier.
Experimentally I manage to make it working using scope=https://outlook.office365.com/.default. I granted full_access_as_app (Office 365 Exchange Online / Application permissions) and got administrator consent for it.
I did face this issue while implementing OAuth for EWS. My application is not using EWS Managed API. Here is what all I did to make it working.
Added permission Office 365 Exchange Online > full_access_as_app to application.
Acquired access token for scope https://outlook.office365.com/.default.
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
form-data = {
client_id,
client_secret,
grant_type: 'client_credentials',
scope: 'https://outlook.office365.com/.default',
};
Added access token as Authorization header and ExchangeImpersonation SOAP header to the request.
<SOAP-ENV:Header>
<t:ExchangeImpersonation>
<t:ConnectingSID>
<t:PrimarySmtpAddress>user#domain.com</t:PrimarySmtpAddress>
</t:ConnectingSID>
</t:ExchangeImpersonation>
</SOAP-ENV:Header>
Late answer, but since this seems to come up, and I was just working with this... why not.
If you use Microsoft's v2.0 URLs for OAUTH2 (https://login.microsoftonline.com/common/oauth2/v2.0/authorize and .../common/oauth2/v2.0/token) then the scope for Office 365 EWS is:
https://outlook.office365.com/EWS.AccessAsUser.All
You'll probably want to combine this scope with "openid" (to get the signed in user's identity) and "offline_access" (to get a refresh token). But then offline_access may not be necessary when using client credentials (because you don't have to prompt a human user for them every time you need an access token).
In other words:
params.add("client_id", "...")
...
params.add("scope", "openid offline_access https://outlook.office365.com/EWS.AccessAsUser.All")
If using v1 OAUTH2 URLs (https://login.microsoftonline.com/common/oauth2/authorize and .../common/oauth2/token) then you can use a "resource" instead of a "scope". The resource for Office 365 is https://outlook.office365.com/.
Or in other words:
params.add("resource", "https://outlook.office365.com/")
Note that in the latter case, you're not asking for any scopes (it's not possible to combine "resource" with scopes). But the token will automatically cover offline_access and openid scopes.
I used this method successfully:
Install Microsoft Authentication Library module ( MSAL.PS)
https://www.powershellgallery.com/packages/MSAL.PS/4.2.1.3
Configure Delegate Access as per MSFT instructions: https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth
Configure ApplicationImpersonation for a service account as normal
Grab your token
$cred = Get-Credential
$clientid = ""
$tenantid = ""
$tok = Get-MsalToken -ClientId $clientid -TenantId $tenantid -UserCredential $cred -Scopes "https://outlook.office.com/EWS.AccessAsUser.All"
I am developing an application based on google Oauth. Now my all authentications are done. Even I have now the access token and secret. Now I dont know how to use this access token and secret.
Please I really need Help on this. I have already done the hard work which is getting access token and secret. Only need to know how to use this token and secret to call an api.
To use the access key/secret you
set inputparameters for the google client service
create a token using the access key and secret, and the inputparameters
set the token in the google client service
In Python, using the gdata library:
self.gd_client.SetOAuthInputParameters(
gdata.auth.OAuthSignatureMethod.HMAC_SHA1,
self.consumer_key, consumer_secret=self.consumer_secret)
oauth_input_params = gdata.auth.OAuthInputParams(
gdata.auth.OAuthSignatureMethod.HMAC_SHA1,
self.consumer_key, consumer_secret=self.consumer_secret)
oauth_token = gdata.auth.OAuthToken(key=access_key,
secret=access_secret,
scopes=gdata.gauth.AUTH_SCOPES,
oauth_input_params=oauth_input_params)
self.gd_client.SetOAuthToken(oauth_token)
After that you can call the service methods to retrieve the data.
Nico