How to write a message to Microsoft Teams with Azure ClientApplication Token - c#

I try to send a message to Microsoft Teams with the graph api and a non-personal ClientApplication token.
It is currently no problem to get the token from Azure by clientId / secret / tenantId. The token seems to work as well, so I'm able to get e.g. the list of all azur users by rest api. But when I try to send a message to Teams with this token (POST https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/messages), the result is
{
"error": {
"code": "Unauthorized",
"message": "Unauthorized",
"innerError": {
"date": "...",
"request-id": "..."
}
}
}
The same call works fine with my personal bearer token.
Any idea how to enable the clientapp to post to teams?

From your description, you are using client credential flow to get the token, then use the token to call Microsoft Graph, if so, the Application permission is needed.
You got the error because the Microsoft Graph - Create chatMessage in a channel does not support the Application permission, it means you could not use the client credential flow in this case.
FAQ:
The token seems to work as well, so I'm able to get e.g. the list of all azure users by rest api.
To call different APIs via this flow, you need different Application permission, this works because your app has the application permission to call the api - List users.
The same call works fine with my personal bearer token.
I suppose you are using another flow to get the token, e.g. auth code flow, ropc flow. These flows need the Delegated permission, which is supported to create chatMessage in a channel.

Related

Azure AD Authentication : Multiple Scopes Issue

I am likely expecting or doing something that is not correct. Need some help in getting back to the right path.
Simple Usecase - How to configure a client so it can request a token with all the right scopes? currently I am running it via postman but actual client is going to be a react app using msal.
Setup:
App Registration in Azure.
API Permissions:
Microsoft.Graph --> email & User.Read
Exposed an API:
Scope URI: api://someguid
One Scope is Added : api//someguid/testscope
Net Core 6 API
AppSettings.Json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Audience":"api//someguid"
"ClientId": "my-client-id",
"TenantId": "my-tenant-id"
},
"Graph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read,email"
}
}
Middleware
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(Configuration.GetSection("Graph"))
.AddInMemoryTokenCaches();
This is how I am calling graph in Controller.
[Authorize]
public class AbcController: Controller
{
private readonly GraphServiceClient _graphClient;
public AbcController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
[HttpGet("get-me")]
public async Task<ActionResult> GetSomeDetails()
{
var user = await _graphClient.Me.Request().GetAsync();
return null;
}
I run this via postman with Auth Code flow with PKCE, Here are the issues
When I set the Scope as : api//someguid/testscope
Call gets authenticated and the Token is acquired correctly in postman
The API get-me get authorized correctly
But the call _graphClient.Me.Request().GetAsync() throws throws a 500 error
Also, a direct call to https://graph.microsoft.com/v1.0/me in postman using the token
gives insufficient privilege error
When I set the scope as : api//someguid/testscope https://graph.microsoft.com/email
Call gets authenticated But the acquire token fails with incorrect scope
When I set the scope as : https://graph.microsoft.com/email https://graph.microsoft.com/user.read
Call gets authenticated and the acquire token is acquired
Direct call to https://graph.microsoft.com/v1.0/me works as expected
But now my API does not get Authorized and gives 401
Can someone suggest what am i missing in my setup or if i am doing something crazy wrong?
All i am looking to do is get my API authorized, and get the email address pulled from graph in the API, without explicitly re-acquiring the token or specifying my client secret in the API to build the graph client.
This was taken as an input to try and build my poc
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-call-api?tabs=aspnetcore
Note that, one token can only be issued to one audience. You cannot acquire access token for multiple audience (Ex: custom API and MS Graph) in single call.
In your scenario, you need to make two separate requests for acquiring access tokens i.e., one for your API and other for Microsoft Graph.
I tried to reproduce the same in my environment via Postman and got below results
I registered one Azure AD application and added same API permissions as below:
Now I exposed one API named testscope same as you like below:
Make sure to select Single-page application while adding Redirect URIs to your application like below:
I acquired token successfully using Auth code flow with PKCE from Postman like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:<appID>
grant_type:authorization_code
scope: api://someguid/testscope
code:code
redirect_uri: https://jwt.ms
code_verifier:S256
The above token won't work for calling Microsoft Graph /me endpoint and works only to authorize API based on its audience.
To check the audience of above token, decode it in jwt.ms like below:
To call /me endpoint for mail, you need to acquire token again with Microsoft graph scope without configuring client secret like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:<appID>
grant_type:authorization_code
scope: https://graph.microsoft.com/.default
code:code
redirect_uri: https://jwt.ms
code_verifier:S256
The above token won't work for authorizing API whereas you can call Microsoft Graph based on its audience.
To check the audience of above token, decode it in jwt.ms like below:
When I call /me endpoint using above token, I got the results successfully with mail like below:
References:
Azure AD Oauth2 implicit grant multiple scopes by juunas
reactjs - azure msal-brower with nextJs: passing only one scope to the API

Microsoft Graph API - how to get access token without Authorization Code?

I want my Web API to get an Access Token to then call Microsoft Graph API. I've gone through a few documents and threads but they all talk about a POST method that asks for a Client ID and App Secret created when registering the app on AAD.
I'm following this document here.
My problem is:
What is client_credentials? Where should I get it from? I thought the API is supposed to be working with the secret and the client I'd only.
I appreciate your help.
There's 4 parameters in the HTTP request:
grant_type: in this case, the value is "client_credentials"
client_id: The client id of your app
client_secret: The client secret of your app
resource: The identifier of the API you want a token for, in this case https://graph.microsoft.com
So only client id and secret are needed from your app.
If you use v2 endpoint / MSAL, note there is no resource parameter.
Instead you would use scope=https://graph.microsoft.com/.default.

Why is this "AccessDenied" while Communicating with Microsoft Graph API?

I cant access endpoints in Microsoft graph.
I will be uploading a file to a users one drive when I have the communication set up
but for now I am just trying to get a list of items in a drive.
This is the response I get from https://graph.microsoft.com/v1.0/drives/{{drive_id}}/root/children
{
"error": {
"code": "AccessDenied",
"message": "Either scp or roles claim need to be present in the token.",
"innerError": {
"request-id": "123",
"date": "2020-01-09T11:43:20"
}
}
}
I retrieved the drive_id by using graph explorer on endpoint https://graph.microsoft.com/v1.0/me/
on the signed in user.
It should be noted that I can use this endpoint https://graph.microsoft.com/v1.0/subscriptions
So I must be doing something right with the access token.
I followed this tutorial to get the access token using the client credential flow.
and this is the code I use to get the access token
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-
form-urlencoded"));
var req = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/<tenant>/oauth2/token");
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", "123"},
{"client_secret", "123"},
{"resource", "https://graph.microsoft.com"}
});
When I decode the JWT I do not see any scopes in the token but if I understand correctly
header should get the scopes that have been permitted by admin.
An admin has already granted my app these permissions for graph explorer:
(Although I do not have the delegate permissions, does that matter?)
Filse.Read,
Files.ReadWriteAll,
Sites.ReadWtiteAll,
User.Read
I am using this collection in postman to test.
Thanks in advance any advice is appreciated.
From Marc LaFleurs' answer :
The first thing to understand is that you cannot receive both Application and Delegated permissions in the same token, it is an either/or scenario. Which type you receive depends entirely on which OAuth Grant you used to request the token:
Authorization Code and Implicit return Delegated tokens with an scp property
Client Credentials return Application tokens with a roles property
The second thing is that you've requested scopes to two different APIs. Based on what you've selected, you won't have access to SharePoint through the Microsoft Graph because you've only requested access to the legacy SharePoint API. More importantly, you've only requested the Delegated User.Read scope for Graph so when you use Client Credentials to obtain the token, that token won't have any permissions.
In order to obtain an Application token for reading SharePoint sites, you'll need Sites.Read.All Microsoft Graph Application permission selected.

How to get OAuth2 access token for EWS managed API in service/daemon application

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"

Validate Access_Token from Microsoft Online OAuth via REST API or OWIN

I am using ASP.NET Web Forms and OWIN to single-sign on into a web service via Microsoft Online. This is done by calling:
app.UseMicrosoftAccountAuthentication(ClientId = ..., ClientSecret = ...)
I have setup a CustomMicrosoftAuthProvider which allows me to get the access token via context.AccessToken.
Great. Now I want to be able to validate this Access_Token. With the Google OAuth API this is really easy - I call a validation endpoint via REST and I am done.
But with Microsoft I cannot find clear/good documentation for how to validate this Access Token? Is there a REST API to pass in an access token and validate it? Or is there a similar API where I can pass in the access token and get the user's email address or get a 401 if it is invalid?
If none of the above exists, is there some way to do this via the ASP.NET OWIN API?
Any help here is greatly appreciated!
Update: I tried making a GET to this URL, where ACCESS_TOKEN is taken from context.AccessToken:
https://apis.live.net/v5.0/me?access_token=ACCESS_TOKEN
However, it produces this error:
{
"error": {
"code": "request_token_invalid",
"message": "The access token isn't valid."
}
}

Categories

Resources