The task is to download google sheet in excel format and store it in Azure blob storage on timely basics using the Azure time trigger function.
Access Method to users google drive - OAuth Client ID.
I have created an Azure function locally and it works fine as expected and performs the task but when I deploy azure function I get this error.
Code for DriveService where the error occurs according to stack trace when deployed
public string[] Scopes = { DriveService.Scope.Drive, DriveService.Scope.DriveReadonly };
public DriveService GetService()
{
UserCredential _credential;
//Error Occurs at line below
Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow googleAuthFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer()
{
ClientSecrets = new ClientSecrets
{
ClientId = _config[Constant.ClientId],
ClientSecret = _config[Constant.ClientSecret],
}
});
string FilePath = Path.GetDirectoryName(_driveCredentialsPath);
_credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
googleAuthFlow.ClientSecrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(FilePath, true)).Result;
DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = _credential,
ApplicationName = Constant.ApplicationName,
});
return service;
}
I think there are two situations where it can go wrong but I am not sure about it.
When I am running the application locally a consent screen appears and gives permission to access the drive.
When this same function is running on azure who and how it will grant permission to access the drive.
I have provided my Azure App URL on Google OAuth Consent Screen as mentioned below to overcome this situation.
When I am running locally after giving permission to access drive it creates a TOKENRESPONSE-USER file a which consists of the access token, expiry date refresh token, and scope.
Is this possible that when the function is deployed it is unable to create a TOKENRESPONSE-USER file on azure function?
Please let me know why I am getting this error or do I need to change something in my process.
You can configure your function app to use Google login for authentication purposes when running on Azure. To achieve this you have to generate client id and client secret using the Google sign-in for server-side apps, using this connection you can store the tokens obtained in the token store. Please refer to this document to configure your function app to use Google Login, refer to this document regarding the token store and how to retrieve and refresh the token obtained.
Related
I am trying to access google drive using the azure function(time-triggered), it creates a token file during runtime when permissions are given to access the drive. It stores that file locally, and the azure function works fine locally.
But when deployed I get an error where my local system path is described in an error that I receive. When I have deployed the function why is it storing my local system path?
It should access the path where the Azure function is stored.
Code
public DriveService GetService()
{
//get Credentials from client_secret.json file
UserCredential credential;
string clientSecretString = config[Constant.ClientSecret];
log.LogInformation("String value is " + clientSecretString);
byte[] clientSecret = Encoding.UTF8.GetBytes(clientSecretString);
using (var stream = new MemoryStream(clientSecret)) <----------------- Error Message
{
log.LogInformation("Current path is " + Environment.CurrentDirectory);
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(Environment.CurrentDirectory, false)).Result;
}
log.LogInformation("Completed ");
//create Drive API service.
DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Constant.ApplicationName,
});
return service;
}
Error Message:
[Error] at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)at System.Threading.Tasks.Task1.GetResultCore(Boolean waitCompletionNotification)at System.Threading.Tasks.Task1.get_Result()at ExportSheetsToExcelPowerBi.GoogleDriveService.GetService() in C:\Users\username\Documents\Project\GoogleDriveService.cs:line
For Azure function apps there is no need to read secrets from the token file generated by Google Auth. As answered on one of your previous posts. You can configure your function app to use Google login for authentication purposes when running on Azure. To achieve this you have to generate client id and client secret using the Google sign-in for server-side apps, using this connection you can store the tokens obtained in the token store. Please refer to this document to configure your function app to use Google Login, refer to this document regarding the token store and how to retrieve and refresh the token obtained.
I'm experimenting with various Azure features and currently want to retrieve a secret from KeyVault.
Straight to the case:
I'm using this nuget package to interact with my azure resources.
I've developed a simple .NET Core console app and run it locally.
I have a KeyVault resource with one secret defined which is active and not expired.
I've registered an App in AAD so my locally shipped .NET Core console app has an identity within AAD.
Than I've created a "client secret" within this registered app in AAD to use it to authenticate myself as an app.
After that I've added access policy in my KeyVault resource to allow GET operation for secrets for this registered app:
Then I've developed a small piece of code which should retrieve the desired secret:
public class AzureAuthentication
{
public async Task<string> GetAdminPasswordFromKeyVault()
{
const string clientId = "--my-client-id--";
const string tenantId = "--my-tenant-id--";
const string clientSecret = "--my-client-secret--";
var credentials = new ClientSecretCredential(tenantId, clientId, clientSecret);
var client = new SecretClient(new Uri("https://mykeyvaultresource.vault.azure.net"), credentials);
var secret = await client.GetSecretAsync("admincreds");
return secret.Value.Value;
}
}
However when I'm trying to do this I'm getting an AccessDenied error:
Am I missing something painfully obvious here? Or there is some latency (>30 min for this moment) for which changes from Access policies screen in KeyVault resource are applied?
I test your code and Get permission, it works fine.
From your screenshot, it looks you didn't add the correct service principal related to the AD App to the Access policies.
If you add the service principal related to the AD App, it will appear as APPLICATION, not COMPOUND IDENTITY.
So when you add it, you could search for the client Id(i.e. application Id) or the name of your App Registration directly, make sure you add the correct one.
Make sure your AD App(service principal) has the correct permission in your keyvault -> Access policies
I am trying to call a Authenticated API from my client app. However, when making AcquireTokenAsync, I get following error "authority_not_in_valid_list: 'authority' is not in the list of valid addresses"
here is my code snippet:
resourceUrl = "https://myApiEndPoint.com";
var clientCredential =
new ClientCredential( myClientAppId, myClientSecretKey );
// myClientAppId and myClientSecretKey are the values from Azure Portal
var authContext =
new AuthenticationContext( "https://my_authority/myApiEndPoint");
return await authContext.AcquireTokenAsync( resourceUrl, clientCredential );
In my azure Portal for my client Id of app, I have granted delegated permission to access https://myApiEndPOint.com api.
Any thoughts on what could be causing this issue and what does it mean by not in valid list?
I understand that:
you created your application in the Azure portal, and therefore the authority is the Azure AD endpoint. Therefore the authority is probably https://login.microsoftonline.com/common? Or do you have good reasons to use "https://my_authority" ?
you have granted delegated permissions to access the API. This means that your application will access the API in the name of the user. However the AcquireTokenAsync method that you use is using the "ClientCredential" flow (meaning with an application secret)
You probably rather want to use another override passing the resourceUri, the clientId, ...
If this is your use case, I suggest you have a look to the active-directory-dotnet-webapi-onbehalfof sample (See here)
i am working on project, where we have service that run computation on Azure Batch in user subscription mode (because we are using custom image). I have now my code fully working, but it requires every launch to provide user credentials to log into Azure Active Directory app before it can create Batch pools and so on. Because it will run as background service, i need to log in silently with some provided user without popup asking user to log in.
I have registered native app in Azure and set its access to Azure Batch service, created Azure AD user, and got all ids and names from it.
Here is my code i am using now.
private const string AuthorityUri = "https://login.microsoftonline.com/common";
private const string BatchResourceUri = "https://batch.core.windows.net";
private const string BatchAccountEndpoint = "https://<BATCH SERVICE NAME>.westeurope.batch.azure.com";
private const string ClientId = "<AZURE APP GUID ID>";
...
public static async Task<string> GetAuthenticationTokenAsync()
{
var authContext = new AuthenticationContext(AuthorityUri);
//here it will throw exception about no token found in cache and to call AquireToken
var authResult = await authContext.AcquireTokenSilentAsync(BatchResourceUri, ClientId, new UserIdentifier("<AD USER GUID ID>", UserIdentifierType.UniqueId));
//this works fine, but show popup dialog for login
/*var authResult = await authContext.AcquireTokenAsync(BatchResourceUri,
ClientId,
new Uri(RedirectUri),
new PlatformParameters(PromptBehavior.Auto));*/
return authResult.AccessToken;
}
...
Func<Task<string>> tokenProvider = () => GetAuthenticationTokenAsync();
using (BatchClient batchClient = await BatchClient.OpenAsync(new BatchTokenCredentials(BatchAccountEndpoint, tokenProvider)))
{
...
}
Classic way with AquireToken with popup for login is working fine. I have tried to use AquireTokenSilent (as is shown in code), but i am getting error about no token cache and need to call AquireToken.
Id used in UserIdentifier is user id guid taken from Azure Active Directory user blade.
Does anybody know, how to update my code so i will be able to silently log into Azure Batch with specified user and is this even possible?
Thanks for help.
AcquireTokenSilent is not meant for this use case. It will try to get the token from the cache where it was previously stored by AcquireTokenAsync.
And AcquireTokenAsync will pop up a login dialog, so you can't use that in your batch app either.
Take a look at either authenticating with a certificate or with username/password.
In the first sample, you need to create a ClientAssertionCertificate with
certCred = new ClientAssertionCertificate(clientId, cert);
this is then used for AcquireTokenAsync:
result = await authContext.AcquireTokenAsync(todoListResourceId, certCred);
The other sample creates a UserPasswordCredential with
var uc = new UserPasswordCredential(user, password);
and then also uses it with AcquireTokenAsync in a slightly different way:
authContext.AcquireTokenAsync(todoListResourceId, clientId, uc);
There are some limitations as to what you can do with the tokens that are based on the two different authentication methods. For example, using the access token for EWS Impersonation requires using the certificate method.
I am writing a simple desktop application that needs to retrieve some basic properties about a user from Microsoft’ directory. Specifically:
I am writing a single tenant native LOB application.
The application runs on my desktop.
The application runs as my logged on domain account.
The organization' domain accounts are synced to AAD.
I am not trying to secure a native web app or a Web API or anything like that. I do not need users to sign in.
I have email addresses of folks in my organization from an external event management tool. I need to lookup the AAD account profile data (address book info - specifically job title) from AAD based on the email address. I will only be reading AAD data.
So far, I have done the following:-
It appears that the Azure AD Graph API is the right way to fetch the profile information. In particular, the information is available at the endpoint: https://graph.windows.net/{tenant}/users/{email}?api-version=1.6
When registering the native application in AAD, no key was provided. So I don't have a client secret.
Looked at the sample in GitHub here: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-console. The instructions here seem to be wrong because no Keys section is available [see (2)].
Based on the sample above, I wrote a simple function. Code is below:
private static async Task PrintAADUserData(string email)
{
string clientId = "0a202b2c-6220-438d-9501-036d4e05037f";
Uri redirectUri = new Uri("http://localhost:4000");
string resource = "https://graph.windows.net/{tenant}";
string authority = "https://login.microsoftonline.com/{tenant}/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
string api = String.Format("https://graph.windows.net/{tenant}/users/{0}?api-version=1.6", email);
LOG.DebugFormat("Using API URL {0}", api);
// Create an HTTP client and add the token to the Authorization header
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.AccessTokenType, authResult.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(api);
string data = await response.Content.ReadAsStringAsync();
LOG.Debug(data);
}
Questions
The application when run was able to bring up the authentication page. Why do I need that? The application already runs as my domain account. Is an additional authentication necessary? If I were to run this application in Azure as a worker process, then I would not want to use my domain credentials.
The primary problem seems to be the resource URL which is wrong. What resource do I need to specify to access the Azure AD Graph API?
Thanks,
Vijai.
EDITS
Based on the comments from #Saca, the code and application has been edited.
Code
string clientId = ConfigurationManager.AppSettings["AADClientId"];
string clientSecret = ConfigurationManager.AppSettings["AADClientSecret"];
string appIdUri = ConfigurationManager.AppSettings["AADAppIdURI"];
string authEndpoint = ConfigurationManager.AppSettings["AADGraphAuthority"];
string graphEndpoint = ConfigurationManager.AppSettings["AADGraphEndpoint"];
AuthenticationContext authContext = new AuthenticationContext(authEndpoint, false);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.windows.net", new ClientCredential(clientId, clientSecret));
ExistingTokenWrapper wrapper = new ExistingTokenWrapper(authResult.AccessToken);
ActiveDirectoryClient client = new ActiveDirectoryClient(new Uri(graphEndpoint), async () => await wrapper.GetToken());
IUser user = client.Users.Where(_ => _.UserPrincipalName.Equals(email.ToLowerInvariant())).Take(1).ExecuteSingleAsync().Result;
App
Error
Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> Microsoft.Data.OData.ODataErrorException: Insufficient privileges to complete the operation. ---> System.Data.Services.Client.DataServiceQueryException: An error occurred while processing this request. ---> System.Data.Services.Client.DataServiceClientException: {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
It appears that despite giving the right permissions, the correct resource and being able to acquire a token, there is still something missing.
The key thing to consider here is if your application will be a headless client run from a secure server or desktop client run by users on their machines.
If the former, then your application is considered a confidential client and can be trusted with secrets, i.e. the keys. If this is your scenario, which is the scenario covered by the sample, then you need to use clientId and clientSecret.
The most likely reason you are not seeing a Keys section in the your application's Configure page is that, instead of selecting Web Application and/or Web API as per step #7 in the sample, you selected Native Client Application when first creating the application. This "type" can't be changed, so you'll need to create a new application.
If your scenario is the latter, then your application is considered a public client and can't be trusted with secrets, in which case, your only options is to prompt the user for credentials. Otherwise, even if your app has it's own authorization layer, it can easily be decompiled and the secret extracted and used.
Your resource URL is correct by the way.
Turns out the real issue was not with the code. I am not an AAD administrator. It appears that any application needing to perform authentication against AAD in our tenant needs to have permissions enabled by the AAD administrators. Once they enabled permissions for my application (and took ownership of the AAD registration as well), this started working.
Hope help some one that are using GraphClient:
var userPriNam = "johndoe#cloudalloc.com";
var userLookupTask = activeDirectoryClient.Users.Where(
user => user.UserPrincipalName.Equals(userPriNam, StringComparison.CurrentCultureIgnoreCase)).ExecuteSingleAsync();
User userJohnDoe = (User)await userLookupTask;
from https://www.simple-talk.com/cloud/security-and-compliance/azure-active-directory-part-5-graph-api/