How do you use AcquireTokenSilentAsync to authenticate a user? - c#

I am attempting to programmatically authorise an Azure application from an Azure AD joined machine.
If I go to the application URL in Internet Explorer it is able to verify the logged on user account.
My current code looks something like this:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
AuthenticationContext context = new AuthenticationContext("https://login.microsoftonline.com/TENANTGUID");
Uri uri = new Uri("urn:ietf:wg:oauth:2.0:oob");
var pparams = new PlatformParameters(PromptBehavior.Auto, null);
AuthenticationResult result = await context.AcquireTokenAsync("https://graph.windows.net", "1950a258-227b-4e31-a9cf-717495945fc2", uri, pparams);
This call is successful but I want to acquire a token for the currently logged on user.
The first two parameters to the AcquireTokenAsync call are resource and clientid.
I can get the Homepage url and application id for the application I want to access but cannot find a combination of the two that works.
What parameters should I pass to this function to silently validate the logged on user and obtain an authorisation header that can be used in subsequent calls to the application?

I'd advise you now MSAL.NET Integrated Windows Authentication for domain or AAD joined machines:
the code would be something like :
static async Task GetATokenForGraph()
{
string tenant = "contoso.com" // can also be a GUID or organizations for multi-tenant
string authority = $"https://login.microsoftonline.com/{tenant}";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication(clientId, authority);
var accounts = await app.GetAccountsAsync();
AuthenticationResult result=null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
try
{
result = await app.AcquireTokenByIntegratedWindowsAuthAsync(scopes);
}
catch (MsalUiRequiredException ex)
{
// For details see the article

Related

OAuth 2.0 With Desktop App. No Microsoft login screen opened

I tried to call a test connection using C#. At the beginning it worked, a few days later, I have deleted the token and tried again => the user authentication / Microsoft login window does not open anymore.
No matter if the settings were wrong or right, the window always opened.
(it did not work even on a completely rebuilt PC)
Problem:
When executing the method "await app.AcquireTokenInteractive(scopes).ExecuteAsync();" It looks like the app is waiting for input in the microsoft login window, but no window opens. Unfortunately there is no response.
My Azure App Configuration:
I registered my app in the Azure portal as "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft account".
1
RedirectUri
For.NET Desktop i used: https://login.microsoftonline.com/common/oauth2/nativeclient
2
In the API permissions i added following permission scopes:
offline_access
email
IMAP.AccessAsUser.All
POP.AccessAsUser.All
SMTP.Send
3
In the line var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync(); it should open the window.
My C# Code:
var app = PublicClientApplicationBuilder
.Create(accessParameters.ClientId)
.WithAuthority(
AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
)
.WithDefaultRedirectUri()
.Build();
TokenCacheHelper.EnableSerialization(app.UserTokenCache);
var scopes = new string[]
{
"offline_access",
"email",
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/POP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
};
string userName;
string accessToken;
var account = (await app.GetAccountsAsync()).FirstOrDefault();
try
{
AuthenticationResult refresh = await app
.AcquireTokenSilent(scopes, account)
.ExecuteAsync();
userName = refresh.Account.Username;
accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
var result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
userName = result.Account.Username;
accessToken = result.AccessToken;
}
string[] acc = { userName, accessToken };
return acc;
It should look like this

AADSTS501051: Application '{API GUID}'(DEV-API) is not assigned to a role for the application '{API GUID}'(DEV-API)

I want to access one API by its Client Credential directly not via any web application
private async Task<string> GetAutheticationToken(string APITypeSelected, string APIKeySelected=null)
{
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenant = ConfigurationManager.AppSettings["ida:AADTenant"];
string appKey = ConfigurationManager.AppSettings[APIKeySelected];
string apiID = ConfigurationManager.AppSettings[APITypeSelected];
//appKey = HttpUtility.UrlEncode(appKey);
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
using (HttpClient client = new HttpClient())
{
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = null;
ClientCredential clientCredential = null;
authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
//encodeURIComponent(client_secret);
clientCredential = new ClientCredential(apiID, appKey);
AuthenticationResult authResult = null;
authResult = await authContext.AcquireTokenAsync(apiID, clientCredential);
return authResult.AccessToken;
}
}
while executing I am getting bellow error(AADSTS501051) in this line
authResult = await authContext.AcquireTokenAsync(apiID, clientCredential);
AADSTS501051: Application '{API GUID}'(DEV-API) is not assigned to a
role for the application '{API GUID}'(DEV-API).
Do I have to give API permission to itself.
What I need to do.
Thanks,
First you need to make a user role for application if app assignment is required. if not there is no problem. If app assignment is required, Go back to api permission and in my api give permission for the created role, see Microsoft documentation url
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-registration
Ahh so you want an access token to the API itself? Not sure if that's possible..
If this in another app, it should be registered as another app in Azure AD.
It can then require application permissions on the API and call it via client credentials.
You can see how to define permissions here: https://joonasw.net/view/defining-permissions-and-roles-in-aad
If this is within the same app, it sounds odd that it would acquire a token for itself.
This error message indicates that you need to add an "App role" to your app registration. You can do so by first adding a new App role on {API GUID}
and then assign the app {API GUID} this role (don't forget to give admin consent)
Essentially what is happening here is that your app registration {API GUID} got a role on {API GUID} to create access tokens for the audience {API GUID}, so: itself.
When you use "authContext.AcquireTokenAsync(apiID, clientCredential);" to get the access token, you need to use identifierUri of your ad application as resource.
For example:
string tenantId = "your tenant id or name, for example: hanxia.onmicrosoft.com";
string clientId = "your client id";
string resource = "the identifierUri of your ad application ";
string clientSecret = "";
ClientCredentia clientCredentia = new ClientCredentia(clientId,clientSecret);
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId);
AuthenticationResult result = context.AcquireTokenAsync(resource, clientCredentia);
For more details, please refer to the document.

"The scope https://graph.microsoft.com/Calendars.Read is not valid" error while getting access token for my app

I am trying to use MS graph API to access user's calendar events but while trying to get the access token for my app that I registered in azure,
I am getting the following error:
Error : The scope https://graph.microsoft.com/Calendars.Read is not
valid.
Below is my code:
string token = string.Empty;
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("ClientID")
.WithTenantId("TenantID")
.WithClientSecret("ClientSecret")
.Build();
string[] scopes = new string[] { "https://graph.microsoft.com/Calendars.Read" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
token = result.AccessToken;
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", token);
return Task.FromResult(0);
}));
var events = await graphServiceClient.Users["user1#onTestMicrosoft.com"].Events.Request().GetAsync();
}
catch (MsalServiceException ex)
{
// Case when ex.Message contains:
// AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
// Mitigation: change the scope to be as expected
}
What am I doing wrong here? I have already granted permission to Calendars.Read in azure portal while registering my app there: https://www.screencast.com/t/jTjnB4SX5I
A couple of things are going on here.
When you use the client credentials flow, you're required to use a scope of the form {resource}/.default, where {resource} is the URL of the thing you want access to. In this case, your scope should be https://graph.microsoft.com/.default. (Source)
You have not configured any application permissions on your app registration. From your screenshot, you've only configured delegated permissions, which are user permissions that require a logged in user. Add Calendars.Read as an application permission and that should get you going.

Swagger UI will return API data, but my Authorized calls return "permission denied"

So I believe my APIservice should be fine since I can return results through Swagger? I am calling from a WPF project. I launch the program and it asks me to login, then it continues and will tell me I don't have permission.
I'm super green to WebAPI2 and think I may just be constructing my call incorrectly. It does seem that I get a token back correctly from my site, the only issue is when I try to actually call on the API for data.
Here is my code:
public static string clientId = "{#Calling App Id}";
public static string commonAuthority = "https://login.windows.net/{#my Azure AD tenant}";
public static Uri returnUri = new Uri("http://MyDirectorySearcherApp");
const string ResourceUri = "https://{#Api App Service}.azurewebsites.net";
public static async Task<List<User>> LoadBands(IPlatformParameters parent)
{
AuthenticationResult authResult = null;
List<User> results = new List<User>();
try {
//get token or use refresh
AuthenticationContext authContext = new AuthenticationContext(commonAuthority);
if (authContext.TokenCache.ReadItems().Count() > 0)
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
authResult = await authContext.AcquireTokenAsync(ResourceUri, clientId, returnUri, parent);
} catch (Exception ee) {
throw ex;
}
using (var httpClient = new HttpClient()) {
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"{ResourceUri}/api/Band/")) {
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
using (var response = await httpClient.SendAsync(request)) {
string responseData = await response.Content.ReadAsStringAsync();
//responseData always equals "You do not have permission to view this directory or page"
return results;
}
}
}
Edit: Maybe helpful to note I'm using a DataAPI that is called by a Rest API, the rest API is secured by Azure AD.
Edit: I'm calling from a Portable Class Library.
Edit: Well, I'm getting authenticated but it does not appear to make any difference. If I completely remove the Auth header I get the same result
It seems that the token is incorrect for the web API which protected by Azure AD. Please check the aud claim in the token which should match the Audience you config in the web API project. You can check the aud claim by parse the token from this site.
And if you still have the problem please share the code how you protect the web API.
Update
If you were using the Express mode like below, you need to acquire the access_token using the app which you associate with the web API.
If you were using the Advanced mode, we should also use the that app to acquire the token and the ResourceUri should matched the value you config in ALLOWED TOKEN AUDIENCES like below:

Server-side task to query Office 365 account for new emails

I need a server-side task on my .NET 4.6.1/MVC 5 app that will periodically check a specific O365 email address for new emails and retrieve them if found. This seems like a stupidly simple task, but I cannot find documentation anywhere for creating a server-side process to accomplish this. The only documentation Microsoft seems to have is for OAuth2 and passing through credentials when users sign in. I don't want that. I want to check one specific account, that's it. How would I accomplish this?
These are the pages I've found. There are others, but all are along these lines.
Intro to the Outlook API - I don't see a way to use a service account with the v2 endpoint.
Get Started with the Outlook REST APIs - This is specific to logging users in with OAuth2, unhelpful for my purposes.
Intro to the Outlook API - I don't see a way to use a service account with the v2 endpoint.
The v2 endpoint doesn’t support client credential at present( refer to the limitation). You need to register/configure the app using Azure portal and use the original endpoint to authenticate the app. More detail about register the app please refer to here. And we need to ‘read mail in all mailbox’ to use the client credential to read the messages like figure below.
And here is the code that using client credential to read messages using the Microsoft Graph:
string clientId = "";
string clientsecret = "";
string tenant = "";
string resourceURL = "https://graph.microsoft.com";
string authority = "https://login.microsoftonline.com/" + tenant + "/oauth2/token";
string userMail = "";
var accessToken = new TokenHelper(authority).AcquireTokenAsync(clientId, clientsecret, resourceURL);
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
}));
var items = await graphserviceClient.Users[user].Messages.Request().OrderBy("receivedDateTime desc").GetAsync();
foreach (var item in items)
{
Console.WriteLine(item.Subject);
}
class TokenHelper
{
AuthenticationContext authContext;
public TokenHelper(string authUri)
{
authContext = new AuthenticationContext(authUri);
}
public string AcquireTokenAsync(string clientId, string secret,string resrouceURL)
{
var credential = new ClientCredential(clientId: clientId, clientSecret: secret);
var result = authContext.AcquireTokenAsync(resrouceURL, credential).Result;
return result.AccessToken;
}
}
In addition, if we authenticate the app with code grant flow we can also create a subscription which notify the app when the mail box receive the new messages.( refer to webhoocks/subscription)

Categories