I am trying to create a simple example of a console Application that requires a valid user. The Application is a simple Console Application:
static void Main(string[] args)
{
string authority = "https://login.windows.net/----------------.onmicrosoft.com";
AuthenticationContext context = new AuthenticationContext(authority);
Console.WriteLine("Context created");
string resource = "ConsoleApp1";
string clientId = "------------------------------------";
string redirectUri = "http://localhost";
var parameters = new PlatformParameters(PromptBehavior.RefreshSession);
context.TokenCache.Clear();
// Throws AggregateException--> AADSTS50105: The signed in user is not assigned to a role for the application
var token = Task.Run(() => context.AcquireTokenAsync(resource, clientId, new Uri(redirectUri), parameters)).Result;
Console.WriteLine("Hello, authorized user");
}
The Application starts, presents the sign-in window, and then throws the exception
AADSTS50105: The signed in user is not assigned to a role for the application
Over in the Azure Portal, I've registered ConsoleApp1 as a native Application.
Under Required Permissions, I've checkmarked:
Sign in and read user profile
Access the Directory as the signed-in user
Other than that, I'm stumped, the error Message tells me that I need to assign some kind of role to the user, but I cannot find this anywhere in the Application's settings. Can someone help shed some light on this please?
You were specify the incorrect resource(ConsoleApp1). The resource should should represents the resource you have granted to this application and which you acquire the access token for.
It seems that you grant Azure AD Graph's permission to this application, did you want to acquire the access token to use the Azure Graph REST?
If I understood correctly, you can replace the resource using https://graph.windows.net.
More detail about integrating with Azure AD, you can refer here.
Related
I am getting the following error when trying to access the directory api by getting a list of users
Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.", Uri:""
Based on my previous googling efforts and reading stack overflow I am not sure what is causing this. As far as I am aware I have everything set up correctly. I have it setup to use the drive api in a very similar fashion and that works perfectly.
My service account does have domain wide delegation which is why I think it may have something to do with the second part of the error. Any ideas what could be causing this?
protected async virtual Task<DirectoryService?> GetDirectoryService()
{
if (currentDirectory == null)
{
string[] scopes = new string[] { DirectoryService.Scope.AdminDirectoryUser };
var initializer = new ServiceAccountCredential.Initializer(configuration["GoogleServiceAccount:AccountEmail"]){Scopes = scopes, User = configuration["GoogleServiceAccount:UserEmail"] };
var cred = new ServiceAccountCredential(initializer.FromPrivateKey(configuration["GoogleServiceAccount:SecretKey"]));
currentDirectory = new DirectoryService(new BaseClientService.Initializer { HttpClientInitializer = cred, ApplicationName = "DriveAPI" });
}
return currentDirectory;
User = configuration["GoogleServiceAccount:UserEmail"]
User is the user on your domain that you want to delegate as not the service accounts email address.
update
Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested
This error message in my exprence normally means that you are using code that does not match the type of credetinals you are using.
There are serval types of authorization, service account, Oauth installed and oauth web (lets ignore mobile for now). The code used for these credentials is different.
So if you use a service account key file with code designed for an Ouath2 installed app. You will normally get "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested"
The code I normally use for delegation with a service account is as follows. it looks similar to yours so i'm inclined to agree that you are using service account code. Which means to me you are probably using the wrong key file. I would double check. Open it it should say "type": "service_account".
// Load the Service account credentials and define the scope of its access.
var credential = GoogleCredential.FromFile(PathToServiceAccountKeyFile)
.CreateWithUser("user#mydomain.com")
.CreateScoped(new[] {DriveService.ScopeConstants.Drive});
My suggestion is to now double check and ensure that you are using the service account key file from google cloud console that was created by a user on your domain, and that you configured domain wide deligation for and have added the admin sdk scopes for remember the OAuth Scopes for admin have to be set, as well configuring an authorized user.
Is it possible to acquire a jwt token for the current application and not based on another Azure app or keep it through the entire lifecycle of signed in?
In my app I'm authenticating my user to Azure with Owin and OpenIdConnect. Authentication succeeded and I received a token from Azure.
In later methods I need to pass the token as parameter, so now I store it in session variable, but this expires earlier and results in an empty variable.
Now I'm trying to acquire a token from Azure with ADAL.
string userObjectId = ((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(c => c.ToString() == "http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigurationManager.AppSettings["ida:AADInstance"] + ConfigurationManager.AppSettings["ida:TenantId"], new TokenCache());
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"], "client_secret");
var result = authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["ida:ClientId"], credential).Result.AccessToken;
This results in an error:
{"error":"invalid_grant","error_description":"AADSTS50105: Application >
'4337e286-7b84-4377-8843-82ea9504606b' is not assigned to a role for the application '4337e286-7b84-4377-8843-82ea9504606b'}
For other apps, it is set up with another app which has permissions for the resource, but I don't want to depend on another app to get the token.
Anyone an idea?
EDIT
I've followed the suggestion of #Jean-MarcPrieur, but on the line
var accounts = await application.GetAccountsAsync();
it returns no accounts which results in an empty result and no accessToken.
Here you are trying to use the client credentials flow (which requires your app to register permissions to call the Web API you want to call - tenant admin permissions).
If you want to access resources in the name of the user, you need to acquire a token for your app to call the API in the name of the signed-in user.
See for instance this sample: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore2-2-signInAndCallGraph which signs-in an user and calls the graph
It's an ASP.NET Core sample using the Azure AD v2.0 endpoint (the latest technos). If you have other constraints, don't hesitate to ask.
I am creating a console application that connects to Microsoft Graph using the Microsoft Graph API (as shown in https://github.com/microsoftgraph/console-csharp-connect-sample).
Everything is working fine, but I wonder if there is a way where I can authenticate a user (when I already know their user/password) without them needing to manually enter their credentials on the "Sing in to your account" window rendered on the desktop.
The idea is basically to run the application unattended, so there is no need for the user to be entering their credentials when the application starts. I canĀ“t find any relevant information on the subject.
Is that even possible?
EDIT
After following the link #DanSilver posted about geting access without a user, I tried the sample suggested in that link (https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2). Although that is an MVC application that forces users to authenticate (precisely what I wanted to avoid) I have managed to use part of the authentication code in that sample with my console application. After giving authorization to the application manually through a request to https://login.microsoftonline.com/myTenantId/adminconsent I can create a GraphServiceClient in my console app that connects to Graph without user interaction. So I mark the answer as valid.
Just in case someone is in the same situation, the GraphServiceclient is created as:
GraphServiceClient graphServiceClientApplication = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string clientId = "yourClientApplicationId";
string authorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
string tenantId = "yourTenantId";
string msGraphScope = "https://graph.microsoft.com/.default";
string redirectUri = "msalXXXXXX://auth"; // Custom Redirect URI asigned in the Application Registration Portal in the native Application Platform
string clientSecret = "passwordGenerated";
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, String.Format(authorityFormat, tenantId), redirectUri, new ClientCredential(clientSecret), null, new TokenCache());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new string[] { msGraphScope });
string token = authResult.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
));
One idea is using the "app only" authorization flow. The idea is that you can have long running apps access the Microsoft Graph without user authentication. The main difference is instead of the access token granting access to a particular user, it grants your app access to resources that you've consented to in advance. There will be no user login dialog and you can programmatically fetch access tokens to call the Graph API.
To reiterate that these tokens aren't for a particular user, consider making a GET request to 'https://graph.microsoft.com/v1.0/me'. This will return an error since the access token isn't for a particular user and "me" doesn't mean anything. Requests should be sent with full user ids "like graph.microsoft.com/users/someuser#contosos.com".
More information on this can be found at the Get access without a user documentation page.
Another idea is to let the user authenticate the first time they use your app and then store a refresh token. These tokens live longer (a few months IIRC) and then you won't need to prompt for user consent each time the app runs. Refresh tokens can be exchanged for access tokens that live 60 minutes and those can be used to call Graph API on behalf of users.
More info on refresh tokens: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user#5-use-the-refresh-token-to-get-a-new-access-token
I did want to come back out here and share, since I ran into this problem yesterday, and the idea of granting read/write mailbox access for my application... to EVERYONE'S EMAIL BOX IN THE ENTIRE ORGANIZATION... was way over the top for my needs. (And that is exactly what happens when you start talking about granting Application level permissions instead of delegated permissions to your registered app).
It's a simple use case: I had a nightly process that needed to automate sending of emails from a shared mailbox using a traditional AD service account.
Thankfully... even though they are on the march to eliminate passwords (lol)... someone at Microsoft still recognizes my use case, and it's lack of apples-to-apples alternatives in Azure AD. There is still an extension method we can lean on to get the job done:
private AuthenticationContext authContext = null;
authContext = new AuthenticationContext("https://login.microsoftonline.com/ourmail.onmicrosoft.com",
new TokenCache());
result = authContext.AcquireTokenAsync("https://graph.microsoft.com/",
"12345678-1234-1234-1234-1234567890",
new UserPasswordCredential(
Environment.GetEnvironmentVariable("UID", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("UPD", EnvironmentVariableTarget.User)
)).Result;
You can replace those GetEnvironmentVariable calls with your Username (UID) and Password (UPD). I just stuff them in the environment variables of the service account so I didn't have to check anything into source control.
AcquireTokenAsync is an extension method made available from the Microsoft.IdentityModel.Clients.ActiveDirectory namespace. From there, it's a simple business to fire up a GraphClient.
string sToken = result.AccessToken;
Microsoft.Graph.GraphServiceClient oGraphClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", sToken);
return Task.FromResult(0);
}));
The last bit of magic was to add these permissions to Application registration I created in Azure AD (where that GUID came from). The application has be defined as a Public client (there's a radio button for that towards the bottom of the authentication tab). I added the following 5 DELEGATED permissions (NOT application permissions):
Microsoft Graph
1. Mail.ReadWrite.Shared
2. Mail.Send.Shared
3. User.Read
4. email
5. openid
Since user consents are actually blocked in our organization, another permissions admin had to review my application definition and then do an admin level grant of those rights, but once he did, everything lit up and worked like I needed: limited access by a service account to a single shared mailbox, with the actual security of that access being managed in Office 365 and not Azure AD.
I am using the following code to try to programmatically register an application in Azure Active Directory:
var application = azure.ActiveDirectoryApplications.Define(applicationName)
.WithSignOnUrl(url)
.WithIdentifierUrl(url)
.WithAvailableToOtherTenants(false)
.DefinePasswordCredential(id)
.WithPasswordValue(secret)
.Attach()
.Create();
Where azure is an instance of Microsoft.Azure.Management.Fluent.Azure.
When I run the above to create an Azure Active Directory Application, an Microsoft.Azure.Management.Fluent.Azure exception is thrown with the message Operation returned an invalid status code 'Forbidden'. Creation of other Azure resources (like resource groups and app services) work just fine.
Looking at the exception details, I can see that a request is made to the following endpoint:
https://graph.windows.net/{myTenantId}/applications?api-version=1.6
The following is in the response body:
{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
Since the bodies says "Insufficient privileges to complete the operation", it appears to be a simple permission issue, but I have granted the following permissions (while signed in as a global administrator) for the Microsoft.Azure.ActiveDirectory API for the application that's running the code:
Access the directory as the signed-in user
Read and write directory data
Are these privileges not enough? What am I missing? As I said, creation of other resources using the fluent API works just fine.
The scope Directory.AccessAsUser.All and Directory.ReadWrite.All User.Read is sufficient permission to create applications in the Azure Active Directory. Since you doesn't provide how you construct the azure instance, I would provide a working code sample:
static void Main(string[] args)
{
var url = "http://adfei.onmicrosoft.com/appFluent";
var id = "abc";
var secret = "secret";
var applicationName = "appFluent";
var credFile = new AzureCredentials(new UserLoginInformation
{
ClientId = "{appId of native application}",
UserName = "{userName}",
Password = "{password}"
},
"adfei.onmicrosoft.com", AzureEnvironment.AzureGlobalCloud);
IAzure azure = Azure.Authenticate(credFile).WithDefaultSubscription();
var application = azure.ActiveDirectoryApplications.Define(applicationName)
.WithSignOnUrl(url)
.WithIdentifierUrl(url)
.WithAvailableToOtherTenants(false)
.DefinePasswordCredential(id)
.WithPasswordValue(secret)
.Attach()
.Create();
Console.Read();
}
And please ensure the scope is include in the access token to ensure that you have the permission for this operation. You can capture the request via Fiddler to check the token and decode the token from this site to check scp claims in the access token.
I registered sample app from Microsoft graph sample app
And standard login is working but when I try to make it simplier by using this code:
var authContext = new AuthenticationContext(Settings.AzureADAuthority);
var token = authContext.AcquireToken(Settings.O365UnifiedAPIResource, new ClientCredential(Settings.ClientId, Settings.ClientSecret)).AccessToken;
I get the following error: Application with identifier '[ClientId here]' was not found in the directory microsoft.com
Setting.O365UnifiedAPIResource = #"https://graph.microsoft.com/";
Settings.AzureADAuthority = #"https://login.microsoftonline.com/common";
Does anyone know what can be the problem?
Settings.AzureADAuthority = #"https://login.microsoftonline.com/{tenant_id or tenant_name}";
When acquiring the token by using the client credential (client id + client secret). You should specify the tenant explicitly.
For example:
https://login.microsoftonline.com/{tenant_id}
or
https://login.microsoftonline.com/{your_domain.onmicrosoft.com}
BTW, as this registration will be for the sample app, it will only have the Mail.Send permission which is delegated permission. To acquire the app token, you also need to grant the app level permission in Azure AD since your are acquiring the app token rather than the user token.