How can I create an Azure Auth Token in a blobTrigger Azure Function? - azure

I am trying to create a new Storage Account resource from an Azure Function. I am trying to use a StorageManagementClient, and need to pass in a ServiceClientCredential.
My code feels too simple... it compiles, but I feel like I must be missing some params. I am passing in a subscriptionId and a tenantId of an account in my subscription.
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://management.azure.com/", tenantId);
ServiceClientCredentials credentials = new TokenCredentials(accessToken);
StorageManagementClient StorageManagement = new StorageManagementClient(credentials) { SubscriptionId = subscriptionId };
When it runs, I get the following error:
2021-02-12T03:32:26.407 [Error] Executed 'BlobTrigger1' (Failed, Id=9c293b8d-591e-420e-b376-dc9ac45097cc, Duration=343ms)Parameters: Connection String: [No connection string specified], Resource: https://management.azure.com/, Authority: https://login.microsoftonline.com/cd256644-73f5-4da4-af5d-4a977f7a6a5d. Exception Message: Tried the following 3 methods to get an access token, but none of them worked.Parameters: Connection String: [No connection string specified], Resource: https://management.azure.com/, Authority: https://login.microsoftonline.com/cd256644-73f5-4da4-af5d-4a977f7a6a5d. Exception Message: Tried to get token using Managed Service Identity. Access token could not be acquired. An attempt was made to access a socket in a way forbidden by its access permissions.Parameters: Connection String: [No connection string specified], Resource: https://management.azure.com/, Authority: https://login.microsoftonline.com/cd256644-73f5-4da4-af5d-4a977f7a6a5d. Exception Message: Tried to get token using Visual Studio. Access token could not be acquired. Visual Studio token provider file not found at "D:\local\LocalAppData\.IdentityService\AzureServiceAuth\tokenprovider.json"Parameters: Connection String: [No connection string specified], Resource: https://management.azure.com/, Authority: https://login.microsoftonline.com/cd256644-73f5-4da4-af5d-4a977f7a6a5d. Exception Message: Tried to get token using Azure CLI. Access token could not be acquired. 'az' is not recognized as an internal or external command,operable program or batch file.

Your code is correct. I have found this issues is similar to yours. You can try this:
Hi friends, I have little ability to write fancy workaround code (my
head is not there), but by what you said above, I harkened back to
some old advice of: In the tool bar at the top of your VS 2017 program
go to: Tools, Options, Azure Service Authentication, Account
Selection, click the drop arrow on the right of the Microsoft banner
with your account name on it, click your account pop-up
again...hard(really insist on it), and that worked. I really feel like
a "just fix it" here might be just fine for a lot of folks. Don't get
me wrong, you girls(guys) know it better. Boy did I just want it to
work (phew!) Thanks :)
If that can't solve your problem, you can try this code:
public static async Task Main(string[] args)
{
string accessToken = await GetAuthorizationHeader();
ServiceClientCredentials credentials = new TokenCredentials(accessToken);
StorageManagementClient StorageManagement = new StorageManagementClient(credentials) { SubscriptionId = subscriptionId };
}
public static async Task<string> GetAuthorizationHeader()
{
ClientCredential cc = new ClientCredential(applicationId, password);
var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
var result = await context.AcquireTokenAsync("https://management.azure.com/", cc);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}

After following instructions on creating an Application Resgistration in Azure AD, this is the code I ended up getting to work:
var azureAuthConnectionString = $"RunAs=App;AppId={appId};TenantId={tenantId};AppKey={appKey}";
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider(azureAuthConnectionString);
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://management.azure.com/");
ServiceClientCredentials credentials = new TokenCredentials(accessToken);
StorageManagementClient StorageManagement = new StorageManagementClient(credentials) { SubscriptionId = subscriptionId };

Related

Authenticate to multiple Azure services using Service Principle (.net Core)

I need to get access to Key Vault and Service Bus from code, using a Service Principle for authentication.
I can use the following code to access Service Bus, which works as expected - when I enable to Service Principle in the Access Policies I can pull the list of topics:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(APPID, APPSECRET, TENANTID, AzureEnvironment.AzureGlobalCloud);
var serviceBusManager = ServiceBusManager.Authenticate(credentials, SUBSCRIPTIONID);
var serviceBusNamespace = serviceBusManager.Namespaces.List().SingleOrDefault(n => n.Name == "SERVICEBUSNAMESPACE");
var topics = serviceBusNamespace.Topics.ListAsync().GetAwaiter().GetResult();
However, I also need to get some information from Key Vault and I was trying to establish a common way to authenticate.
METHOD 1
Similar to the above, I tried this code to access KeyVault:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(APPID, APPSECRET, TENANTID, AzureEnvironment.AzureGlobalCloud);
var kvManager = new KeyVaultClient(credentials);
var secret = kvManager.GetSecretAsync("https://VAULTNAMESPACE.vault.azure.net", "SECRETNAME").GetAwaiter().GetResult().Value;
I get the the following error:
Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: 'Operation
returned an invalid status code 'Unauthorized''
METHOD 2
This code does work for Key Vault however (showing I have correct permissions):
string GetSecret()
{
var client = new KeyVaultClient(GetAccessToken);
var secret = client.GetSecretAsync("https://VAULTNAMESPACE.vault.azure.net", "SECRETNAME").GetAwaiter().GetResult();
return secret;
}
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
var credential = new ClientCredential(appId, appSecret);
var tokenResult = await context.AcquireTokenAsync("https://vault.azure.net", credential);
return tokenResult.AccessToken;
}
But, again, it's a very KeyVault specific way to Authenticate and I was hoping to establish a common mechanism using SdkContext.AzureCredentialsFactory. Any reason why I'd be getting an Unauthorized exception with the code above connecting to Key Vault? (all is set up correctly in Azure).
Thanks for any tips!
When you use SdkContext.AzureCredentialsFactory.FromServicePrincipal to authenticate, it will use https://management.azure.com/ as its Resource Uri.
While Azure Key Vault has its own authorization system and its Resource Uri is https://vault.azure.net, so you may get the Unauthorized error message.
So, you could use Method2 to get access to Azure Key Vault with right Resource Uri.
For more details, you could refer to this article.

AADSTS70001: Application is not supported for this API version

I have an ASP.NET MVC web application that will need to check if a user is member of a specific group in an Azure Active Directory. To achieve this I will use Microsoft Graph API, so I downloaded their example to try it out from here and got it running fine.
My next step is to get it running with my own AppId, AppSecret and RedirectUri and this is where I get in trouble. In Azure I went to "App registrations" for the AAD and assured that the application was added. I opened the app and copied the "Application ID" for the AppId, created a key and used it as the AppSecret. I checked all the permissions and pressed "Grant Permissions" and added my URL to the Reply URLs. I also changed the Authority attribute from https://login.microsoftonline.com/common/v2.0 to https://login.windows.net/xxxxxx.onmicrosoft.com.
When I run the application and press "Sign in" I will come to the login screen correctly, but it will crash when I try to sign in. The crash occurs when running AcquireTokenByAuthorizationCodeAsync in the code below:
AuthorizationCodeReceived = async (context) =>
{
var code = context.Code;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
new SessionTokenCache(signedInUserID, context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase));
string[] scopes = graphScopes.Split(new char[] { ' ' });
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(scopes, code);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
The error message I get in AuthenticationFailed looks like this:
AADSTS70001: Application 'xxxxxxxx-4f81-4508-8dcb-df5b94f2290f' is not supported for this API version. Trace ID: xxxxxxxx-d04c-4793-ad14-868810f00c00 Correlation ID: xxxxxxxx-ab83-4805-baea-8f590991ec0c Timestamp: 2017-06-13 10:43:08Z
Can someone explain to me what the error message means? Should I try a different version? I have tried with both Microsoft.Graph v1.3.0 / Microsoft.Graph.Core v1.4.0 and Microsoft.Graph v1.4.0 / Microsoft.Graph.Core v1.5.0.
You are trying to use MSAL with the v1 endpoints. This is evident from using ConfidentialClientApplication. You need to use ADAL (Azure AD Authentication Library) instead. Or register the app for v2 at https://apps.dev.microsoft.com.
You can get ADAL from NuGet: https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/
You can find a sample app using ADAL here: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
Your OnAuthorizationCodeReceived will need to look something like this:
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
// If you create the redirectUri this way, it will contain a trailing slash.
// Make sure you've registered the same exact Uri in the Azure Portal (including the slash).
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
}

Resource not found for the segment 'me'

i'm using Graph API to retrieve profile information of user who's currently logged in from the Azure AD, unfortunately i'm receiving the following error message :
{"odata.error":{"code":"Request_ResourceNotFound","message":{"lang":"en","value":"Resource not found for the segment 'me'."}}}
Below is my code :
Uri serviceRoot = new Uri(serviceRootURL);
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
var user = (User)await adClient.Me
.Expand(x => x.Manager)
.ExecuteAsync();
And below is my code for GetAppTokenAsync() :
private static async Task<string> GetAppTokenAsync()
{
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resAzureGraphAPI, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
From your code "await GetAppTokenAsync()" , you are getting an app-only token , which using application identity, instead of as a user's identity .
The "(User)await adClient.Me" won't work if that token is not associated with a user .
To use app token to get user manager information ,you need to specify the user you want to query , code below is for your reference :
try
{
User manager = (User)await adClient.Users.GetByObjectId("5eba8883-c258-45d0-8add-a286a1ec1e91").Manager.ExecuteAsync();
}
catch (Exception ex)
{
throw;
}
Update
You could use authorization code flow for delegated permissions(user's identity) . If you want a client library code sample , you could refer to this code sample . After user sign in , you could use below code to get manager of current login user :
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
User manager = (User)await client.Me.Manager.ExecuteAsync();
I used an application identity with the legacy Azure Active Directory api and the 'Application.ReadWrite.OwnedBy' permission to work around the Resource not found for the segment 'me' error. The same permission exists in the Microsoft Graph api, but the behavior is not identical. More information here.

Application access to SharePoint Online using Azure AD Token

How can I get an application token to query SharePoint with application credentials (= without user impersonation) using Azure AD?
The following code works perfectly for querying data as a user but we need to fetch information without impersonation like listing all sites in the collection regardless of user permissions etc.
Exception thrown:
An exception of type
'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException'
occurred in mscorlib.dll but was not handled in user code
Additional information: AADSTS70001: Application with identifier 'xxx'
was not found in the directory sharepoint.com
Code to get token:
internal static async Task<string> GetSharePointAccessToken(string url, string userAccessTokenForImpersonation)
{
string clientID = #"<not posted on stack overflow>";
string clientSecret = #"<not posted on stack overflow>";
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common");
// Use user assetion if provided, otherwise use principal account
AuthenticationResult authResult = null;
if (string.IsNullOrEmpty(userAccessTokenForImpersonation))
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred);
}
else
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred, new UserAssertion(userAccessTokenForImpersonation));
}
return authResult.AccessToken;
}
Test code:
// Auth token from Bearer https://xxx.azurewebsites.net/.auth/me
string authHeader = #"<valid jwt bearer token from azure auth>";
var sharePointUrl = #"https://xxx.sharepoint.com/sites/testsite/";
string sharePrincipalToken = await GetSharePointAccessToken(sharePointUrl, null); // <-- doesn't work
string sharePointUserToken = await GetSharePointAccessToken(sharePointUrl, authHeader); // <-- works
Permissions in Azure AD:
The error message you are getting implies that you are signing in with a user that is pointing our token service to get a token in the context of "sharepoint.com"
This is because you are using the "common" endpoint. Read more about that here.
Instead try using a fixed endpoint, where the tenant is the same as where the application is registered and see if that solves your issue.
If your plan is to make this application accessible by multiple tenants, make sure that you have explicitly set your application to be multi-tenant, and then make sure you have a user from the external tenant try and sign into the application before you try doing service to service calls.
Let me know if this helps.

invalid grant when trying to get token for azure AD graph api

I am fighting invalid_grant error when exchanging code to graph token in OIDC middleware:
I have had stuff like this working before, but cant seem to find the difference form this and the samples. heres the code to try get a token for azure AD Graph api:
AuthorizationCodeReceived = async context =>
{
try
{
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("oid").Value;
string tenantID = context.AuthenticationTicket.Identity.FindFirst("tid").Value;
var credential = GetCredential();
var authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID));
Uri redirectUri = new Uri(context.Request.Uri.GetLeftPart(UriPartial.Path));
Logger.InfoFormat("redirect {0}", redirectUri);
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
context.Code, redirectUri, credential, graphResourceID);
}catch(Exception ex)
{
Logger.ErrorException("oidc", ex);
}
}
but got a
1/17/2015 4:22:42 AM: a0e69d43-1c91-4069-8d1f-4b03103dc227 - AsyncMethodBuilderCore: Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException: AADSTS70002: Error validating credentials. AADSTS70000: The provided access grant is invalid or malformed.
Trace ID: 2112568f-fbe6-4ac6-bd67-de5904a9b9f5
Correlation ID: a0e69d43-1c91-4069-8d1f-4b03103dc227
Timestamp: 2015-01-17 04:22:42Z
Application permission is set to read data.
delegated permission is set to read user profile.
Am I right that above is not sufficient, that i also need to create a authorization request for the graph resource after the user signed in.
Answer is make sure you handle your tailing slashes correctly!
The redirect must be the same as when you started the token exchange, this means that when you start the redirect to authorization endpoint, the redirect uri used there needs to be the same as when you exchange code to token at token endpoint.
A good way to do this is by using
RedirectToIdentityProvider = async (context) =>
{
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
}
which i also did, but i forgot the tailing slash. Now it will be identical to what you get when doing
Uri redirectUri = new Uri(context.Request.Uri.GetLeftPart(UriPartial.Path));

Resources