I have a C# Azure Web Application that accesses Azure Active Directory to get data from users and groups.
After 1h of activity, I get the following error:
{"odata.error":{"code":"Authentication_ExpiredToken","message":{"lang":"en","value":"Your access token has expired. Please renew it before submitting the request."},"date":"2018-03-16T16:17:59","requestId":"cfa18a20-3c2c-4806-ac36-9d4e9ba7738c","values":null}}
Here is my method to get a ActiveDirectoryClient:
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.Azure.ActiveDirectory.GraphClient.Extensions;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
private ActiveDirectoryClient CreateGraphClient()
{
string tenantId = WebConfigurationManager.AppSettings["ida:TenantId"],
applicationId = WebConfigurationManager.AppSettings["ida:ClientId"],
secret = WebConfigurationManager.AppSettings["ida:ClientSecret"],
authority = WebConfigurationManager.AppSettings["ida:AADInstance"] + tenantId,
resrouce = "https://graph.windows.net",
token = string.Empty;
ClientCredential credential = new ClientCredential(applicationId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
try
{
token = authContext.AcquireTokenAsync(resrouce, credential).Result.AccessToken;
}
catch (Exception)
{
token = authContext.AcquireTokenSilentAsync(resrouce, credential.ClientId).Result.AccessToken;
}
Uri baseServiceUri = new Uri(resrouce);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(new Uri(baseServiceUri, tenantId), async () => await Task.FromResult(token));
return activeDirectoryClient;
}
I think the server should refresh the token but it's not happenning. I'm using ADAL 3.x so the 'refresh_token' doesn't exist anymore (I guess).
I know this try/catch is not the way but it was a test.
Any help?
Related
I am trying to call AcquireTokenAsync it is working properly but after sometime it is not responding and it is not providing any result.
please refer the below code how to solve my issue
public static async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(WebConfigurationManager.AppSettings["ClientId"],
WebConfigurationManager.AppSettings["ClientSecret"]);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token");
return result.AccessToken;
}
public static string GetKeyVaultSecret(string secretName)
{
try
{
var secretUri = WebConfigurationManager.AppSettings["SecretUri"];
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var secret = kv.GetSecretAsync(secretUri, secretName).Result;
return secret.Value;
}
catch(Exception ex)
{
return null;
}
}
For access token, the default time is 1 hour. After 1 hour, the client must use the refresh token to (usually silently) acquire a new refresh token and access token.
You can change access token lifetime to the maximum to one day with this tutorial.
New-AzureADPolicy -Definition #('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"24:00:00","MaxAgeSessionSingleFactor":"02:00:00"}}') -DisplayName "WebPolicyScenario" -IsOrganizationDefault $false -Type "TokenLifetimePolicy"
For more details about token lifetime you can refer to this article.
I am trying to get my mail on premises using GraphServiceClient. I am getting the token from Microsoft online account then use that token to get my emails. But because my mails are on premises I cannot access the as it throws the following error "code": "ResourceNotFound", "message": "Resource could not be discovered.". So how can I change my code to use the same token I get from online to on premises Mail using GraphServiceClient (if it is possible).
public async Task<ActionResult> Inbox()
{
string token = await GetAccessToken();
if (string.IsNullOrEmpty(token))
{
// If there's no token in the session, redirect to Home
return Redirect("/");
}
GraphServiceClient client = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
return Task.FromResult(0);
}));
var mailResults = await
client.Me.MailFolders.Inbox.Messages.Request()
.OrderBy("receivedDateTime DESC")
.Select("subject,receivedDateTime,from")
.Top(10)
.GetAsync();
return View(mailResults.CurrentPage);
}
public async Task<string> GetAccessToken()
{
string accessToken = null;
// Load the app config from web.config
string appId = ConfigurationManager.AppSettings["ida:AppId"];
string appPassword = ConfigurationManager.AppSettings["ida:AppPassword"];
string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
string[] scopes = ConfigurationManager.AppSettings["ida:AppScopes"]
.Replace(' ', ',').Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
// Get the current user's ID
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
if (!string.IsNullOrEmpty(userId))
{
// Get the user's token cache
SessionTokenCache tokenCache = new SessionTokenCache(userId, HttpContext);
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId, redirectUri, new ClientCredential(appPassword), tokenCache.GetMsalCacheInstance(), null);
// Call AcquireTokenSilentAsync, which will return the cached
// access token if it has not expired. If it has expired, it will
// handle using the refresh token to get a new one.
IEnumerable<IAccount> accounts = await cca.GetAccountsAsync();
IAccount fisrtAccount = accounts.FirstOrDefault();
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes, fisrtAccount);
accessToken = result.AccessToken;
}
return accessToken;
}
By default you can't access any on-prem information with the Microsoft Graph. The only exception to that is a preview feature in Exchange 2016 that allows you to access Calendar, Mail and Contact items transparently.
This feature has a number of infrastructure pre-requisites so I suggest you connect with your infrastructure team first to validate those. Once those pre-requisites are in place, you should not have to change any code from the application's perspective to access the content. Keep in mind this is a preview feature, so not supported for production workloads.
I have tried the following code to create a new storage account in Azure:
Getting the token (success - I received a token):
var cc = new ClientCredential("clientId", "clientSecret");
var context = new AuthenticationContext("https://login.windows.net/subscription");
var result = context.AcquireTokenAsync("https://management.azure.com/", cc);
Create cloud storage credentials:
var credential = new TokenCloudCredentials("subscription", token);
Create the cloud storage account (fails):
using (var storageClient = new StorageManagementClient(credentials))
{
await storageClient.StorageAccounts.CreateAsync(new StorageAccountCreateParameters
{
Label = "samplestorageaccount",
Location = LocationNames.NorthEurope,
Name = "myteststorage",
AccountType = "RA-GRS"
});
}
Error:
ForbiddenError: The server failed to authenticate the request. Verify
that the certificate is valid and is associated with this
subscription.
I am not sure if this is one of those misleading messages or if I misconfigured something in Azure?
As far as I know, Azure provides two types of storage management library now.
Microsoft.Azure.Management.Storage
Microsoft.WindowsAzure.Management.Storage
Microsoft.Azure.Management.Storage is used to create new ARM storage.
Microsoft.WindowsAzure.Management.Storage is used to create classic ARM storage.
I guess you want to create the new arm storage but you used the "Microsoft.WindowsAzure.Management.Storage" library. Since the "Microsoft.WindowsAzure.Management.Storage" uses the certificate to auth requests, you will get the error. If you want to know how to use "Microsoft.WindowsAzure.Management.Storage" to create classic storage, I suggest you refer to this article.
I assume you want to create new ARM storage, I suggest you install the "Microsoft.Azure.Management.Storage" Nuget package.
More details, you could refer to the following code.
static void Main(string[] args)
{
var subscriptionId = "your subscriptionId";
var clientId = "your client id";
var tenantId = "your tenantid";
var secretKey = "secretKey";
StorageManagementClient StorageManagement = new StorageManagementClient(new Microsoft.Azure.TokenCloudCredentials(subscriptionId, GetAccessToken(tenantId, clientId, secretKey)));
var re= StorageManagement.StorageAccounts.CreateAsync("groupname", "sotrage name",new Microsoft.Azure.Management.Storage.Models.StorageAccountCreateParameters() {
Location = LocationNames.NorthEurope,
AccountType = Microsoft.Azure.Management.Storage.Models.AccountType.PremiumLRS
},new CancellationToken() { }).Result;
Console.ReadKey();
}
static string GetAccessToken(string tenantId, string clientId, string secretKey)
{
var authenticationContext = new AuthenticationContext($"https://login.windows.net/{tenantId}");
var credential = new ClientCredential(clientId, secretKey);
var result = authenticationContext.AcquireTokenAsync("https://management.core.windows.net/",
credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
var token = result.Result.AccessToken;
return token;
}
I am using the code below to fetch user from the azure AD using the graph API, but somehow I am getting the token access issue while doing so.
static async void MakeRequest()
{
var client = new HttpClient();
var queryString = HttpUtility.ParseQueryString(string.Empty);
/* OAuth2 is required to access this API. For more information visit:
https://msdn.microsoft.com/en-us/office/office365/howto/common-app-authentication-tasks */
// Specify values for the following required parameters
queryString["api-version"] = "1.6";
// Specify values for path parameters (shown as {...})
// var uri = "https://graph.windows.net/microsoft.onmicrosoft.com/users/{v-sidmis#microsoft.com}?" + queryString;
var uri = "https://graph.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/users?api-version=1.6";
var response = await client.GetAsync(uri);
if (response.Content != null)
{
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
This code is taken up from TechNet.
It depends on how you want to acquire the token. There are lots of scenario to integrate the application with Azure AD. You can refer it from here.
For example, if you want to use the Azure AD Graph in a daemon or service application, we can use the Client Credential flow.
1 . First we need to register an web application on the portal( detail steps refer here) and grant the permission to read the directory data like figure below:
2 . And then we can get the clientId, secret, tenantId from the portal and use the code below to acquire token(need to install the Active Directory Authentication Library)
string authority = "https://login.microsoftonline.com/{tenantId}";
string clientId = "";
string secret = "";
string resrouce = "https://graph.windows.net";
var credential = new ClientCredential(clientId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
var token = authContext.AcquireTokenAsync(resrouce, credential).Result.AccessToken;
Console.WriteLine(token);
3 . Then we can use this token to call the Azure AD Graph REST directly or we can use the graph client library for Azure AD to retrieve the users. Here is the code samples for your reference:
//use the Azure AD client library
string accessToken = "";
string tenantId = "";
string graphResourceId = "https://graph.windows.net";
Uri servicePointUri = new Uri(graphResourceId);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
ActiveDirectoryClient client = new ActiveDirectoryClient(serviceRoot, async () => await Task.FromResult(accessToken));
foreach(var user in client.Users.ExecuteAsync().Result.CurrentPage)
Console.WriteLine(user.DisplayName);
//using the HTTP request
var client = new HttpClient();
var tenantId = "";
var uri = $"https://graph.windows.net/{tenantId}/users?api-version=1.6";
var token = "";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
var response = client.GetAsync(uri).Result;
var result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
Update
The secrecy is available for the web application/web API when you create an application. Then you can generate the key by keys section like figure below. After you save the app, you can copy the secrect now.
I am trying to get a ActiveDirectoryClient in a C# client, like this:
Uri servicePointUri = new Uri("https://graph.microsoft.com/v1.0/me/messages");
Uri serviceRoot = new Uri(servicePointUri, <OUR-AZURE-TENANT-ID>);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
async () => await AcquireTokenAsyncForUser());
With this AcquireTokenAsyncForUser() method:
public static async Task<string> AcquireTokenAsyncForUser()
{
return await GetTokenForUser();
}
public static async Task<string> GetTokenForUser()
{
if (TokenForUser == null)
{
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/common/v2.0");
UserPasswordCredential userCredential = new UserPasswordCredential("<USERNAME>#outlook.com", <PASSWORD>);
AuthenticationResult userAuthnResult = await authenticationContext.AcquireTokenAsync("https://graph.microsoft.com/v1.0/me/messages",
<AZURE AD APP CLIENT ID>, userCredential);
TokenForUser = userAuthnResult.AccessToken;
Console.WriteLine("\n Welcome " + userAuthnResult.UserInfo.GivenName + " " +
userAuthnResult.UserInfo.FamilyName);
}
return TokenForUser;
}
I keep getting this error:
Error getting signed in user
accessing_ws_metadata_exchange_failed: Accessing WS metadata exchange failed-
Response status code does not indicate success: 406 (NotAcceptable).-
It does not matter if I use correct or incorrect credentials.
AAD does not support WS-Trust sign in for MSA accounts. You have to sign in the user via webview by calling
AcquireTokenAsync("https://graph.microsoft.com/v1.0/me/messages",
<AZURE AD APP CLIENT ID>, new Uri("<your redirect uri>", new PlatformParameters(PromptBehavior.Auto{or whatever you want}, null));