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.
Related
I'm fairly confident I set everything up right for a multi-tenant app.
In Tenant A:
I created an Azure function and enabled a system managed identity. I granted the Managed Identity permissions to the Graph API. I confirmed my API can obtain an access token for the Managed Identity and access the Graph API with it. I added Azure AD authentication to the app and created an App Registration for the app. I configured the necessary Graph API permissions in the API Permissions settings of the app registration. I also enabled the various options to enable multi-tenant access.
In Tenant B:
I accessed the special URL to start the admin consent process for the app in Tenant A. I confirmed that I was prompted to consent to the permissions that I specified in the app registration in Tenant A. I can see that a couple new enterprise application entries were created in Tenant B for the app in Tenant A.
So far so good. However, no matter what I do, I cannot obtain a token to access the graph API in the context of Tenant B. It ALWAYS gives me a token for the managed identity in Tenant A, and accesses the info in Tenant A.
The code the retrieves an access token using the managed identity is here:
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { AdditionallyAllowedTenants = { "*" }, TenantId = "<Tenant B ID>" });
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }, null, null, "<Tenant B ID"));
var accessToken = token.Token;
Now, I have tried all sorts of different combinations here. But, from everything I read, I am supposed to be able to specify the Tenant ID of another tenant in these methods and obtain an access token for that specific tenant. However, even when I specify Tenant B's ID, I always get a token back for the managed identity in Tenant A. If I use the token, of course, I end up accessing all of Tenant A's information.
Ironically, if I remove the DefaultAzureCredentialOptions and include the tenantID in the GetToken request I get an error telling me that I'm trying to obtain a token for a different tenant (than my own) and that I need to add the AdditionallyAllowedTenants option. It clears the error up when I add that, but then it doesn't obtain a token for the other tenant.
Perhaps I am still approaching this wrong. I want to host a multi-tenant Azure Function that runs in the context of the Tenant where the request originated from to access information within that tenant using a managed identity that exists within that tenant. I can obtain the Tenant ID context from the claims sent to the Azure function during authentication, but no matter how I try to specify that Tenant ID in my code, it will not get a token for that Tenant.
UPDATE and SOLUTION:
Thanks to Philippe's great write up, I did finally get this. I had some confusion around the mechanisms at play here and want to add this note for clarity on how I solved it.
If you've followed most of the documents you have an Azure Function app in your "host" tenant A, with an associated app registration in tenant A. The app registration has certain API permissions configured, and you have consented to this app and permissions in Tenant B.
To access the resources in Tenant B, you need to create a secret key or certificate on the app registration in Tenant A. You then need to authenticate using the Client ID, and secret key/certificate of the app registration in Tenant A, but you will request a token for Tenant B. Here it is in code using a certificate for authentication:
var appToken = new ClientCertificateCredential(tenantID, appID, appCert, new ClientCertificateCredentialOptions { AdditionallyAllowedTenants = { "*" } });
var graphServiceClient = new GraphServiceClient(appToken);
Here, we assign the following to the variables:
tenantID = Tenant B's ID
appID = Tenant A's App registration client ID
appCert = Tenant A's App registration cert
We have to include the AdditionallyAllowedTenants config parameter, and the * authorizes a token from any tenant. We then take that credential and use it to build a new GraphServiceClient that is connected to Tenant B.
You've created two separate and independent identities for your service:
The managed identity: You don't need to deal with credentials, but this identity can only be used within the same tenant. You can't use your managed identity to directly access another tenant, and you can't (currently) use your managed identity to authenticate as your app registration.
The app registration: This identity can be used to access data in another tenant, but (currently) you do need to deal with credentials.
The DefaultAzureCredential will attempt different ways of authenticating until it finds one that works:
First, it first tries to authenticate using an EnvironmentCredential. This will look for certain environment variables needed to authenticate. Depending on the variables it finds, it will end up creating a ClientSecretCredential (to authenticate using the app's client secret) or ClientCertificateCredential (using an app's client certificate).
Next, if no suitable environment variables were found, it tries to authenticate using ManagedIdentityCredential, which (as the name suggests) will attempt to use a managed identity. For you, this will always succeed (because there is a managed identity available to be used).
Next, if no managed identity was available, it will continue trying with various other options. This is all described in detail in the documentation.
For your scenario, you basically have two options today.
Option 1, using DefaultAzureCredential and environment variables
Place the credentials for your multi-tenant app registration in environment variables and your existing code will "just work".
For example:
AZURE_TENANT_ID - the target tenant where you want to obtain the token
AZURE_CLIENT_ID - your multi-tenant app registration's app ID
AZURE_CLIENT_SECRET - a client secret for your multi-tenant app registration
Note: In general, it is better to use a certificate than a secret.
Option 2, using ClientSecretCredential or ClientCertificateCredential
Instead of using DefaultAzureCredential, you can directly create a ClientSecretCredential or a ClientCertificateCredential. You'll need to store the credential somewhere safe.
For example, on approach you could follow which would avoid any credentials in environment variable or in code:
Store credentials for your multi-tenant app in a key vault.
Allow your function's managed identity to access the credential in the key vault.
In your function, use your managed identity to retrieve the credential from Key Vault (e.g. using ManagedIdentityCredential), in your tenant.
Use that credential to authenticate as your multi-tenant app registration, in the target tenant (i.e. ClientSecretCredential or ClientCertificateCredential)
https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme
Managed Identities can only get tokens within the tenant that they exist in.
You'd need a "traditional" multi-tenant app registration + client certificate/secret for this case.
I have app with ASP .Net MVC. I need to get tenant wise subscription list from Azure service management api. This can done by -> https://learn.microsoft.com/en-us/rest/api/resources/subscriptions/list#code-try-0
I can get token with 'user_impersonation' scope from my web app. To that token I can only get default account tenant subscription list only. But I want subscription list from another tenant that I have access from my account.
Microsoft get those tenant wise token array from https://token.learn.microsoft.com/accesstokens. Its receive array of tokens for each tenant that I have access for subscriptions. Those tokens works totally fine for getting tenant wise subscription list.
MS API DOC Multi tenant Screenshot
Please refer the code that I use to get tokens.
string token = await GeAccessToken(new string[] { "https://management.core.windows.net//user_impersonation" });
private async Task<string> GetAccessToken(string[] scopes)
{
IConfidentialClientApplication cc = MsalAppBuilder.BuildConfidentialClientApplication();
IAccount userAccount = await cc.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
Microsoft.Identity.Client.AuthenticationResult result = await cc.AcquireTokenSilent(scopes, userAccount).ExecuteAsync();
return result.AccessToken;
}
I need to know how to get that token array from my application. How to get all Azure AD account list using token generated by user_impersonation scope on /common endpoint (same question. but there is no answer for that)
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.
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.
Another attempt to ask a different question surrounding my month long problem:
I'm now trying to initiate the "On-Behalf-Of" flow to get a MS Graph token when users login with a Microsoft Account. As documented here:
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-on-behalf-of
On the client, users log in with a server-flow:
var user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
On the .NET backend, I am trying to retrieve the MS Graph token:
string clientId = "id-shown-in-app-registration-portal";
string clientSecret = "secret-shown-in-app-registration-portal";
IEnumerable<string> msIdTokenOut = null;
Request.Headers.TryGetValues("x-ms-token-microsoftaccount-access-token", out msIdTokenOut);
string msIdToken = msIdTokenOut.FirstOrDefault();
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common/oauth2/v2.0");
UserAssertion assertion = new UserAssertion(msIdToken);
ClientCredential cred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", cred, assertion);
I get the following error:
aadsts50027: invalid jwt token. token format not valid.
I've tried every possible combination, from using server-flow to login, using MSAL for client-flow (which doesn't authenticate against App Services with the retrieved token). This has been driving me crazy for over a month. I can't believe how many hoops I have jumped through to get 2 Microsoft products working together. If anyone can steer me towards a solution I would be beyond grateful.
Here is a workaround, I suggest you could enable the mobile server custom authentication by using MSAL returned access token.
More details, you could refer to below steps:
Firstly, you could create a login page which will use MSAL login with the microsoft account. It will return the access token.
Then you could send the request with the access token to the mobile service backend to ask for authentication.
Notice: The logic in the backend which used to check the access token is right, you need achieve by yourself. You could decode the access jwt token to get the aud value. If this value is as same as the client id that means the user have the permission to access mobile backend data.
Then you could use jwt token to get the user information from graph api. After get the user information, you could set the user information value to claims to generate the auth token(using this method AppServiceLoginHandler.CreateToken[Add Microsoft.Azure.Mobile.Server.Login NuGet package]). By using this token the mobile client user could access the mobile backend.
The access token like this:
More details, you could refer to this article to know how to enable custom auth in mobile backend.