So I've been working for a couple days attempting to create a working example of an application grabbing data, such as a list of the user's contacts, and display it. My attempts so far have be unsuccessful. I have followed a few guides and have read many forums but I can't seem to get them to work.
I got the feeling I have one of the following issues.
Either my application or I do not have the proper permissions on my employer's domain
I am missing a concept
I have a grammatical error in my code.
I have incorrect settings in portal.azure.com or manage.windowsazure.com for my application
As for the where my asp.net project kept breaking here is the code, look at var authResult = await authContext.AcquireTokenSilentAsync(discServResouceId, cc, userid);. I got the original from here AuthenticationHelper
internal class AuthenticationHelper
{
internal static async Task<OutlookServicesClient> EnsureOutlookServicesClientCreatedAsync(string capabilityName)
{
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
var discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var cid = SettingsHelper.ClientId;
var appkey = SettingsHelper.AppKey;
var discServResouceId = SettingsHelper.DiscoveryServiceResourceId;
var cc = new ClientCredential(cid, appkey);
var userid = new UserIdentifier(userObjectId, UserIdentifierType.UniqueId);
var authResult = await authContext.AcquireTokenSilentAsync(discServResouceId, cc, userid);
//AcquireTokenSilentAsync is where my application throws an
//AdalException with the error code "AdalError.FailedToAcquireTokenSilently"
return authResult.AccessToken;
});
var dcr = await discClient.DiscoverCapabilityAsync(capabilityName);
return new OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.AppKey),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
}
catch (AdalException exception)
{
//Handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
Debug.Print(exception.ErrorCode);
authContext.TokenCache.Clear();
//throw exception;
}
return null;
}
catch (Microsoft.Office365.Discovery.DiscoveryFailedException ex)
{
Debug.Print(ex.Message);
return null;
}
}
}
As far as I know everything is being given to AcquireTokenSilentAsync correctly. For that reason I think it's a permission issue with active directory. Any help is appreciated.
Related
I'm getting the following error when trying to use a cached token. I'm using the list that was provided as an example here: https://learn.microsoft.com/bs-latn-ba/azure/active-directory/develop/msal-net-acquire-token-silently
cannot convert from 'System.Collections.Generic.IEnumerable' to 'Microsoft.Identity.Client.IAccount'`
static async Task<GraphServiceClient> Auth()
{
var clientApp = PublicClientApplicationBuilder.Create(ConfigurationManager.AppSettings["clientId"]).Build();
string[] scopes = new string[] { "user.read" };
string token = null;
var app = PublicClientApplicationBuilder.Create(ConfigurationManager.AppSettings["clientId"]).Build();
AuthenticationResult result = null;
var accounts = await app.GetAccountsAsync();
result = await app.AcquireTokenSilent(scopes, accounts)
.ExecuteAsync();
token = result.AccessToken;
GraphServiceClient graphClient = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}));
return graphClient;
}
The call that is causing the error is this one
result = await app.AcquireTokenSilent(scopes, accounts)
.ExecuteAsync();
AcquireTokenSilent requires two variables, IEnumerable<String> (scops) and string (loginHint). What you are passing for loginHist is an array of accounts when it's supposed to be a single string (single account).
var accounts = await app.GetAccountsAsync();
GetAccountsAsync()'s return type is IEnumerable which is why its throwing the error at that call.
In an example here, the account looked up is from the list.
// Get the account
IAccount account = await application.GetAccountAsync(accountIdentifier).ConfigureAwait(false);
// Special case for guest users as the Guest oid / tenant id are not surfaced.
if (account == null)
{
if (loginHint == null)
throw new ArgumentNullException(nameof(loginHint));
var accounts = await application.GetAccountsAsync().ConfigureAwait(false);
account = accounts.FirstOrDefault(a => a.Username == loginHint);
}
Add this line to your code:
var accounts = await app.GetAccountsAsync();
IAccount account = accounts.FirstOrDefault(a => a.Username == loginHint); // <--- Get Account from accounts
result = await app.AcquireTokenSilent(scopes, account) // Use account instead of accounts.
.ExecuteAsync();
token = result.AccessToken;
We are using Azure AD to authenticate and get the refreshed access token,We invoke below method to get Office 365 user Contacts
But when executing the above we are getting exception like "failed_to_acquire_token_silently"
Can anyone help that where we are doing wrong!
List<string> myContacts = new List<string>();
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId,
new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(SettingsHelper.ClientId,
SettingsHelper.ClientSecret),
new Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier(userObjectId,
Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
var dcr = await discClient.DiscoverCapabilityAsync("Contacts");
Microsoft.Office365.OutlookServices.OutlookServicesClient exClient = new Microsoft.Office365.OutlookServices.OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId,
new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(SettingsHelper.ClientId,
SettingsHelper.ClientSecret),
new Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier(userObjectId,
Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
var contactsResult = await exClient.Me.Contacts.ExecuteAsync();
do
{
var contacts = contactsResult.CurrentPage;
foreach (var contact in contacts)
{
myContacts.Add(new MyContact { Name = contact.DisplayName });
}
contactsResult = await contactsResult.GetNextPageAsync();
} while (contactsResult != null);
}
catch (Microsoft.IdentityModel.Clients.ActiveDirectory.AdalException exception)
{
//handle token acquisition failure
if (exception.ErrorCode == Microsoft.IdentityModel.Clients.ActiveDirectory.AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
//handle token acquisition failure
}
}
return View(myContacts);
Thanks in advance
I've successfully managed to list all of the secrets in an Azure KeyVault - however I need to make a call to get a token each time I want to get the next secret.
How do I store the credentials so I only have to login once during the loop?
public async Task<List<string>> getsecretslist(string url)
{
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
List<string> secretlist = new List<string>();
var all = kv.GetSecretsAsync(url);
var myId = "";
foreach (Microsoft.Azure.KeyVault.Models.SecretItem someItem in all.Result)
{
myId = someItem.Id;
var mOtherThing = someItem.Identifier;
var yep = await kv.GetSecretAsync(mOtherThing.ToString());
secretlist.Add(yep.Value);
}
return secretlist;
}
In your GetToken callback method you need to cache the access token as long as it is valid and not expired. Then your callback will return the cached access token instead of doing the authentication again. The following code snippet will use the ADAL default token cache (e.g. TokenCache.DefaultShared).
public static async Task<string> GetToken(string authority, string resource, string scope)
{
var assertionCert = new ClientAssertionCertificate(clientId, certificate);
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, assertionCert).ConfigureAwait(false);
return result.AccessToken;
}
The best way that i found is to save the token you obtained in your GetToken function, for example:
var authenticationContext = new AuthenticationContext(authority, TokenCache.DefaultShared);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resource, KeyVaultUserClientId, new Uri(KeyVaultRedirectUri), new PlatformParameters(PromptBehavior.SelectAccount)).ConfigureAwait(false);
return authenticationResult.AccessToken;
Then i simply altered the getter for the client so it will check for the expiry, if its still valid (should have expiration of 60 minutes) it will return a simpler client which returns the lastAuthenticationResult
private static KeyVaultClient KeyVaultClient
{
get
{
if (lastAuthenticationResult != null && DateTime.UtcNow.AddSeconds(5) < lastAuthenticationResult.ExpiresOn)
{
if (m_cachedKeyVaultClient != null)
{
return m_cachedKeyVaultClient;
}
else
{
return new KeyVaultClient(getCachedToken);
}
}
if (m_keyVaultClient == null)
m_keyVaultClient = new KeyVaultClient(GetAccessTokenAsync);
return m_keyVaultClient;
}
}
private static async Task<string> getCachedToken(string authority, string resource, string scope)
{
return lastAuthenticationResult.AccessToken;
}
You don't need to call GetSecretAsync inside your loop. The secrets are already included in your Result set from calling GetSecretsAsync. This is why you are being authenticated repeatedly.
Here is a simple change to your loop to do what you are looking for.
var all = kv.GetSecretsAsync(url).GetAwaiter().GetResult();
foreach (var secret in all.Value)
{
secretlist.Add(secret.Id);
}
I am setting up a multi tenant application and I am having issues creating a GraphServiceClient.
I have to following AuthorizationCodeReceived:
AuthorizationCodeReceived = async context =>
{
var tenantId =
context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/"+ tenantId);
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
context.Code,
new Uri("http://localhost:21925"),
new ClientCredential(ClientId, ClientSecret),
"https://graph.microsoft.com");
}
This works perfectly to authenticate the user. I am using fiddler, and I see that a new bearer token was given by login.microsoftonline.com/{tenantid}/oauth2/token
When creating a new Graph Service Client I use the following factory method:
public IGraphServiceClient CreateGraphServiceClient()
{
var client = new GraphServiceClient(
new DelegateAuthenticationProvider(
async requestMessage =>
{
string token;
var currentUserId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var currentUserHomeTenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/" + currentUserHomeTenantId + "/");
var clientCredential = new ClientCredential(_configuration.ClientId, _configuration.ClientSecret);
try
{
var authenticationResult = await authenticationContext.AcquireTokenSilentAsync(
GraphResourceId,
clientCredential,
new UserIdentifier(currentUserId, UserIdentifierType.UniqueId));
token = authenticationResult.AccessToken;
}
catch (AdalSilentTokenAcquisitionException e)
{
var result = await authenticationContext.AcquireTokenAsync(GraphResourceId, clientCredential);
token = result.AccessToken;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}));
return client;
}
This method always throws an AdalSilentAcquisitionException and the AcquireTokenAsync retrieves a new token.
With this token, I am not able to request 'Me' on the graph.
I get the following exception: message=Resource 'some guid' does not exist or one of its queried reference-property objects are not present.
However, if I am debugging and I change the token before it is passed to the header, with the value of the one I got previously right after login in (received from login.microsoftonline.com/{tenantid}/oauth2/token ) then the API call works.
Does anyone know what I am doing wrong? He can I get the acquiretokensilently working?
Update: I have updated the code samples. I have removed the custom cache, and now everything seems to work.
How can I make a custom cache based on the http sessions, making sure the AcquireTokenSilently works.
Preview of not working token cache:
public class WebTokenCache : TokenCache
{
private readonly HttpContext _httpContext;
private readonly string _cacheKey;
public WebTokenCache()
{
_httpContext = HttpContext.Current;
var claimsPrincipal = (ClaimsPrincipal) HttpContext.Current.User;
_cacheKey = BuildCacheKey(claimsPrincipal);
AfterAccess = AfterAccessNotification;
LoadFromCache();
}
private string BuildCacheKey(ClaimsPrincipal claimsPrincipal)
{
var clientId = claimsPrincipal.FindFirst("aud").Value;
return $"{claimsPrincipal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value}_TokenCache";
}
private void LoadFromCache()
{
var token = _httpContext.Cache[_cacheKey];
if (token == null) return;
Deserialize((byte[]) token);
}
private void AfterAccessNotification(TokenCacheNotificationArgs args)
{
if (!HasStateChanged) return;
if (Count > 0)
{
_httpContext.Cache[_cacheKey] = Serialize();
}
else
{
_httpContext.Cache.Remove(_cacheKey);
}
HasStateChanged = false;
}
}
I am trying use the code above and it works well form me.
Please ensure that the GraphResourceId is https://graph.microsoft.com(This resource is requested first time in your startUp class) since the method AcquireTokenSilentAsync will try to retrieve the token from cache based on the resrouce.
I'm writing a program to allow a user to upload files to their Google Drive account. I have the upload part working and am using OAuth2. The issue I'm currently having is getting a list of folders from the users Drive account.
I found some code that is supposed to do this using the .setUserCredentials method, but it doesn't work:
DocumentsService service1 = new DocumentsService("project");
service1.setUserCredentials("user","pass");
FolderQuery query1 = new FolderQuery();
// Make a request to the API and get all documents.
DocumentsFeed feed = service1.Query(query1);
// Iterate through all of the documents returned
foreach (DocumentEntry entry in feed.Entries)
{
var blech = entry.Title.Text;
}
Nothing is returned. Ideally, I want to use OAuth2 to do this. I've been trying with the following code, trying to set the authentication token, but I always get denied access:
String CLIENT_ID = "clientid";
String CLIENT_SECRET = "secretid";
var docprovider = new NativeApplicationClient(GoogleAuthenticationServer.Description, CLIENT_ID, CLIENT_SECRET);
var docstate = GetDocAuthentication(docprovider);
DocumentsService service1 = new DocumentsService("project");
service1.SetAuthenticationToken(docstate.RefreshToken);
FolderQuery query1 = new FolderQuery();
DocumentsFeed feed = service1.Query(query1); //get error here
// Iterate through all of the documents returned
foreach (DocumentEntry entry in feed.Entries)
{
// Print the title of this document to the screen
var blech = entry.Title.Text;
}
..
private static IAuthorizationState GetDocAuthentication(NativeApplicationClient client)
{
const string STORAGE = "storagestring";
const string KEY = "keystring";
string scope = "https://docs.google.com/feeds/default/private/full/-/folder";
// Check if there is a cached refresh token available.
IAuthorizationState state = AuthorizationMgr.GetCachedRefreshToken(STORAGE, KEY);
if (state != null)
{
try
{
client.RefreshToken(state);
return state; // Yes - we are done.
}
catch (DotNetOpenAuth.Messaging.ProtocolException ex)
{
}
}
// Retrieve the authorization from the user.
state = AuthorizationMgr.RequestNativeAuthorization(client, scope);
AuthorizationMgr.SetCachedRefreshToken(STORAGE, KEY, state);
return state;
}
Specifically, I get "Execution of request failed: https://docs.google.com/feeds/default/private/full/-/folder - The remote server returned an error: (401) Unauthorized".
I've also tried:
var docauth = new OAuth2Authenticator<NativeApplicationClient>(docprovider, GetDocAuthentication);
DocumentsService service1 = new DocumentsService("project");
service1.SetAuthenticationToken(docauth.State.AccessToken);
but "State" is always null, so I get a null object error. What am I doing wrong and how is this done?
You should use the Drive SDK, not the Documents List API, which allows you to list folders. You can use "root" as a folderId if you want to list the root directory.
I actually implemented the v3 version of the GDrive SDK for .NET and needed to search for folders as well.
I prefer requesting uniquely all folders instead of getting all files and then performing a LinQ query to keep just the folders.
This is my implementation:
private async Task<bool> FolderExistsAsync(string folderName)
{
var response = await GetAllFoldersAsync();
return response.Files
.Where(x => x.Name.ToLower() == folderName.ToLower())
.Any();
}
private async Task<Google.Apis.Drive.v3.Data.FileList> GetAllFoldersAsync()
{
var request = _service.Files.List();
request.Q = "mimeType = 'application/vnd.google-apps.folder'";
var response = await request.ExecuteAsync();
return response;
}
You could request the name on the Q this way as well:
request.Q = $"mimeType = 'application/vnd.google-apps.folder' and name = '{folderName}'";
Which would lead and simplify things to (obviating null checking):
private async Task<bool> FolderExistsAsync(string folderName)
{
var response = await GetDesiredFolder(folderName);
return response.Files.Any();
}
private async Task<FileList> GetDesiredFolder(string folderName)
{
var request = _service.Files.List();
request.Q = $"mimeType = 'application/vnd.google-apps.folder' and name = '{folderName}'";
var response = await request.ExecuteAsync();
return response;
}
private IEnumerable<DocumentEntry> GetFolders(string id) {
if (IsLogged) {
var query = new FolderQuery(id)
{
ShowFolders = true
};
var feed = GoogleDocumentsService.Query(query);
return feed.Entries.Cast<DocumentEntry>().Where(x => x.IsFolder).OrderBy(x => x.Title.Text);
}
return null;
}
...
var rootFolders = GetFolders("root");
if (rootFolders != null){
foreach(var folder in rootFolders){
var subFolders = GetFolders(folder.ResourceId);
...
}
}
where GoogleDocumentsService is a instance of DocumentsService and IsLogged is a success logged flag.
I got this way to get list of folders from google drive
FilesResource.ListRequest filelist= service.Files.List();
filelist.Execute().Items.ToList().Where(x => x.MimeType == "application/vnd.google-apps.folder").ToList()