MSAL TokenAcquisition from the token stored in the file - azure-active-directory

I have saved initial usertakencache in a file. Now when I want to acquire the AcquireTokenSilent, I dont know how to use that saved token cache because all the documentation I find are used on the basis of cache but not for other memory formats.
I have searched on number of microsoft MSAL repos but they do not provide any help with the solution.
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = null;
StreamReader sr = new StreamReader("D:\\Test.txt");
string line = sr.ReadLine();
var accounts = await app.GetAccountsAsync();
string[] scopes = { "Mail.Read" };
try
{
// try to get token silently
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
ViewBag.Relogin = "true";
return View();
}
catch (Exception eee)
{
ViewBag.Error = "An error has occurred. Details: " + eee.Message;
return View();
}
if (result != null)
{
// Use the token to read email
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
HttpResponseMessage hrm = await hc.GetAsync("https://graph.microsoft.com/v1.0/me/messages");
string rez = await hrm.Content.ReadAsStringAsync();
ViewBag.Message = rez;
}

Related

Tenant to tenant user migration in Azure Active Directory using Graph API

Is it possible to migrate users using the MS Graph API in Azure AD?
If so, please explain how to migrate users from one tenant to the other using the MS Graph API.
You can export the users with MS Graph. Note, you can't export the passwords. This means that you have to create a new password and share it with the users. Or choose a random password and let the users reset their password using the self-service password rest feature.
Here is an example how to export the users from a directly
public static async Task ListUsers(GraphServiceClient graphClient)
{
Console.WriteLine("Getting list of users...");
DateTime startTime = DateTime.Now;
Dictionary<string, string> usersCollection = new Dictionary<string, string>();
int page = 0;
try
{
// Get all users
var users = await graphClient.Users
.Request()
.Select(e => new
{
e.DisplayName,
e.Id
}).OrderBy("DisplayName")
.GetAsync();
// Iterate over all the users in the directory
var pageIterator = PageIterator<User>
.CreatePageIterator(
graphClient,
users,
// Callback executed for each user in the collection
(user) =>
{
usersCollection.Add(user.DisplayName, user.Id);
return true;
},
// Used to configure subsequent page requests
(req) =>
{
var d = DateTime.Now - startTime;
Console.WriteLine($"{string.Format(TIME_FORMAT, d.Days, d.Hours, d.Minutes, d.Seconds)} users: {usersCollection.Count}");
// Set a variable to the Documents path.
string filePrefix = "0";
if (usersCollection.Count >= 1000000)
{
filePrefix = usersCollection.Count.ToString()[0].ToString();
}
page++;
if (page >= 50)
{
page = 0;
string docPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"users_{filePrefix}.json");
System.IO.File.WriteAllTextAsync(docPath, JsonSerializer.Serialize(usersCollection));
}
Thread.Sleep(200);
return req;
}
);
await pageIterator.IterateAsync();
// Write last page
string docPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"users_all.json");
System.IO.File.WriteAllTextAsync(docPath, JsonSerializer.Serialize(usersCollection));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
After you export the users, you can import them back to the other tenant. The following example creates test users. Change the code to set the values from the files you exported earlier. Also, this code uses batch with 20 users in single operation.
public static async Task CreateTestUsers(GraphServiceClient graphClient, AppSettings appSettings, bool addMissingUsers)
{
Console.Write("Enter the from value: ");
int from = int.Parse(Console.ReadLine()!);
Console.Write("Enter the to value: ");
int to = int.Parse(Console.ReadLine()!);
int count = 0;
Console.WriteLine("Starting create test users operation...");
DateTime startTime = DateTime.Now;
Dictionary<string, string> existingUsers = new Dictionary<string, string>();
// Add the missing users
if (addMissingUsers)
{
// Set a variable to the Documents path.
string docPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "users.json");
if (!System.IO.File.Exists(docPath))
{
Console.WriteLine("Can't find the '{docPath}' file.");
}
string usersFile = System.IO.File.ReadAllText(docPath);
existingUsers = JsonSerializer.Deserialize<Dictionary<string, string>>(usersFile);
if (existingUsers == null)
{
Console.WriteLine("Can't deserialize users");
return;
}
Console.WriteLine($"There are {existingUsers.Count} in the directory");
}
List<User> users = new List<User>();
// The batch object
var batchRequestContent = new BatchRequestContent();
for (int i = from; i < to; i++)
{
// 1,000,000
string ID = TEST_USER_PREFIX + i.ToString().PadLeft(7, '0');
if (addMissingUsers)
{
if (existingUsers.ContainsKey(ID))
continue;
}
count++;
try
{
var user = new User
{
DisplayName = ID,
JobTitle = ID.Substring(ID.Length - 1),
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = "userName",
Issuer = appSettings.TenantName,
IssuerAssignedId = ID
},
new ObjectIdentity
{
SignInType = "emailAddress",
Issuer = appSettings.TenantName,
IssuerAssignedId = $"{ID}#{TEST_USER_SUFFIX}"
}
},
PasswordProfile = new PasswordProfile
{
Password = "1",
ForceChangePasswordNextSignIn = false
},
PasswordPolicies = "DisablePasswordExpiration,DisableStrongPassword"
};
users.Add(user);
if (addMissingUsers)
{
Console.WriteLine($"Adding missing {ID} user");
}
// POST requests are handled a bit differently
// The SDK request builders generate GET requests, so
// you must get the HttpRequestMessage and convert to a POST
var jsonEvent = graphClient.HttpProvider.Serializer.SerializeAsJsonContent(user);
HttpRequestMessage addUserRequest = graphClient.Users.Request().GetHttpRequestMessage();
addUserRequest.Method = HttpMethod.Post;
addUserRequest.Content = jsonEvent;
if (batchRequestContent.BatchRequestSteps.Count >= BATCH_SIZE)
{
var d = DateTime.Now - startTime;
Console.WriteLine($"{string.Format(TIME_FORMAT, d.Days, d.Hours, d.Minutes, d.Seconds)}, count: {count}, user: {ID}");
// Run sent the batch requests
var returnedResponse = await graphClient.Batch.Request().PostAsync(batchRequestContent);
// Dispose the HTTP request and empty the batch collection
foreach (var step in batchRequestContent.BatchRequestSteps) ((BatchRequestStep)step.Value).Request.Dispose();
batchRequestContent = new BatchRequestContent();
}
// Add the event to the batch operations
batchRequestContent.AddBatchRequestStep(addUserRequest);
// Console.WriteLine($"User '{user.DisplayName}' successfully created.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

Validating an Azure AD generated JWT signature and algorithm in .NET Core 3.1

I am new to Azure AD. We are using v1.0 token. I have an Azure JWT token validation routine mostly based on ValidateSignature and AzureTokenValidation
The below is my ClaimsTransformer:
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
<do something>
var token = httpContextAccessor.HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
if (Validate(token))
{
<proceed to add claims>
}
and my validation routine:
public bool Validate(string token)
{
string stsEndpoint = "https://login.microsoftonline.com/common/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters parameters = new TokenValidationParameters
{
ValidIssuer = "https://sts.windows.net/<mytenant-id>",
ValidAudience = <my client-id>,
IssuerSigningKeys = config.SigningKeys,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
bool verified;
try
{
var handler = tokendHandler.ValidateToken(token, validationParameters, out jwt);
var signature = ((System.IdentityModel.Tokens.Jwt.JwtSecurityToken)jwt).RawSignature;
string algorithm = ((System.IdentityModel.Tokens.Jwt.JwtSecurityToken)jwt).Header["alg"].ToString();
if (signature.Length == 0 || algorithm.ToLower().Equals("none"))
{
tokenVerified = false;
}
tokenVerified = true;
}
catch
{
tokenVerified = false;
}
return tokenVerified;
}
Please tell me if I am doing the right thing or can I just use (in my Validate(string token)
try
{
var result = tokendHandler.ValidateToken(token, validationParameters, out _);
verified = true;
}
catch {
verified = false;
}
and is, the checking for algorithm (to not accept "none" in alg) and signature presence is required or this way to check is right? There is no "secret keys"
JwtBearer middleware, like the OpenID Connect middleware in web apps, validates the token based on the value of TokenValidationParameters. The token is decrypted as needed, the claims are extracted, and the signature is verified. The middleware then validates the token by checking for this data:
Audience: The token is targeted for the web API.
Sub: It was issued for an app that's allowed to call the web API.
Issuer: It was issued by a trusted security token service (STS).
Expiry: Its lifetime is in range.
Signature: It wasn't tampered with.
For more details refer this document
Example:
String token = "Token";
string myTenant = "Tenant Name";
var myAudience = "Audience";
var myIssuer = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/v2.0", myTenant);
var mySecret = "Secrete";
var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(mySecret));
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", myTenant);
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = myAudience,
ValidIssuer = myIssuer,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = false,
IssuerSigningKey = mySecurityKey
};
var validatedToken = (SecurityToken)new JwtSecurityToken();
// Throws an Exception as the token is invalid (expired, invalid-formatted, etc.)
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
Console.WriteLine(validatedToken);
Console.ReadLine();
OUTPUT

dotnet-core WebApp and multiple web api's access tokens using AzureB2C, MSAL

I have setup authentication/authorization for WebApp and Api and its working fine. The problem is when I have to introduce additional Api's which will be called from WebAPP.
The limitation is that you cannot ask a token with scopes mixing Web apis in one call. This is a limitation of the service (AAD), not of the library.
you have to ask a token for https://{tenant}.onmicrosoft.com/api1/read
and then you can acquire a token silently for https://{tenant}.onmicrosoft.com/api2/read as those are two different APIS.
I learned more about this from SO here and here
Since there is no full example other than couple of lines of code, I'm trying to find best way of implementing this solution.
Currently I have setup Authentication in Startup
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
services.AddAzureAdB2C(options => Configuration.Bind("AzureAdB2C", options)).AddCookie();
AddAzureAdB2C is an customized extension method from Samples.
public static AuthenticationBuilder AddAzureAdB2C(this AuthenticationBuilder builder, Action<AzureAdB2COptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsSetup>();
builder.AddOpenIdConnect();
return builder;
}
public class OpenIdConnectOptionsSetup : IConfigureNamedOptions<OpenIdConnectOptions>
{
public void Configure(OpenIdConnectOptions options)
{
options.ClientId = AzureAdB2COptions.ClientId;
options.Authority = AzureAdB2COptions.Authority;
options.UseTokenLifetime = true;
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
OnRemoteFailure = OnRemoteFailure,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived
};
}
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
var defaultPolicy = AzureAdB2COptions.DefaultPolicy;
if (context.Properties.Items.TryGetValue(AzureAdB2COptions.PolicyAuthenticationProperty, out var policy) &&
!policy.Equals(defaultPolicy))
{
context.ProtocolMessage.Scope = OpenIdConnectScope.OpenIdProfile;
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(defaultPolicy.ToLower(), policy.ToLower());
context.Properties.Items.Remove(AzureAdB2COptions.PolicyAuthenticationProperty);
}
else if (!string.IsNullOrEmpty(AzureAdB2COptions.ApiUrl))
{
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
}
return Task.FromResult(0);
}
}
I guess the scope has to be set on this line for each API but this is part of pipeline.(in else if part of OnRedirectToIdentityProvide method above)
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
Following are api client configuration
services.AddHttpClient<IApiClient1, ApiClient1>()
.AddHttpMessageHandler<API1AccessTokenHandler>();
services.AddHttpClient<IApiClient2, ApiClient2>()
.AddHttpMessageHandler<API2AccessTokenHandler>();
Following is the code for acquiring token silently for API1.
public class API1AccessTokenHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IConfidentialClientApplication publicClientApplication = null;
try
{
// Retrieve the token with the specified scopes
scopes = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
publicClientApplication = ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, _httpContextAccessor.HttpContext).EnablePersistence(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
result = await publicClientApplication.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
}
if (result.AccessToken== null)
{
throw new Exception();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Following is the code for acquiring token silently for API2, API2AccessTokenHandler.
public class API2AccessTokenHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IConfidentialClientApplication publicClientApplication = null;
try
{
// Retrieve the token with the specified scopes
scopes = Constants.Api2Scopes.Split(' ');
string signedInUserID = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
publicClientApplication = ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, _httpContextAccessor.HttpContext).EnablePersistence(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
result = await publicClientApplication.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
}
if (result.AccessToken== null)
{
throw new Exception();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Passing the scope while acquiring the token did not help. The token
is always null.
The account always have scope for Api1 but not for
Api2.
The scope of APi1 is added from the AzureB2COptions.ApiScope
as part of the ServiceCollection pipeline code in Startup.cs
I guess having separate calls to Acquire token is not helping in case of Api2 because scope is being set for Api1 in Startup.cs.
Please provide your valuable suggestions along with code samples.
UPDATE:
I'm looking something similar to WithExtraScopeToConsent which is designed for IPublicClientApplication.AcquireTokenInteractive. I need similar extension for ConfidentialClientApplicationBuilder to be used for AcquireTokenByAuthorizationCode
cca.AcquireTokenByAuthorizationCode(AzureAdB2COptions.ApiScopes.Split(' '), code)
.WithExtraScopeToConsent(additionalScopeForAPi2)
.ExecuteAsync();
Yes, we can have multiple scopes for same api not multiple scopes from different Apis.
In this sample, we retrieve the token with the specified scopes.
// Retrieve the token with the specified scopes
var scope = new string[] { api1_scope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
var accessToken=result.AccessToken;
You can get the accessToken with different api scope.
// Retrieve the token with the specified scopes
var scope = new string[] { api2_scope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
var accessToken=result.AccessToken;

TokenCache: No matching token was found in the cache, Azure AD Api

I'd like to use Azure AD Api and I couldn't acquire token some reason. I have two methods, and I got this after calling:
TokenCache: No matching token was found in the cache iisexpress.exe Information: 0
Here's my code:
public string GetToken()
{
string authority = "https://login.microsoftonline.com/{tenantId}/";
string clientId = "";
string secret = "";
string resource = "https://graph.windows.net/";
var credential = new ClientCredential(clientId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
//I think the problem is here:
var token = authContext.AcquireTokenAsync(resource, credential).Result.AccessToken;
return token;
}
public string MakeRequest()
{
string accessToken = GetToken();
var 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);
var client1 = new HttpClient();
var uri = "https://graph.windows.net/" + tenantId + "/users?api-version=1.6";
client1.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var response = client1.GetAsync(uri).Result;
var result = response.Content.ReadAsStringAsync().Result;
return result;
}
I don't know what's the problem, and I didn't find any great hint, under other questions and a little explanation would be helpful. I'd like to understand this part, of course.
//When you are calling
Main() { Method_A() }
aync Method_A() { await Method_B() }
Task < T > Method_B() { return T; }
//It will through the error. //Need to keep Mehtod_B in another Task and run.
// Here I am avoiding few asyncs
Main() { Method_A() }
Method_A() { Method_B().Wait() }
Task Method_B() { return T; }
There is no output using the Console.WriteLine in a IIS progress. If you want to output the result in a output window for the web project, you can use System.Diagnostics.Debug.WriteLine() method.

your access token is expired, please renew it before submitting the request while adding user to group

I am using the below code to get the access token.
It is working for some time and then throwing the error like
your access token is expired please renew it before submitting the request
public static async Task<string> GetTokenForUser()
{
string strAutherizationString = ConfigurationManager.AppSettings["AuthrizationURL"].ToString();
string strResourceURL = ConfigurationManager.AppSettings["ResourceURL"].ToString();
string strDirectoryId = ConfigurationManager.AppSettings["DirectoryId"].ToString();
string strClientId = ConfigurationManager.AppSettings["ApplicationId"].ToString();
string strSecretKey = ConfigurationManager.AppSettings["SecurityKey"].ToString();
AuthenticationContext authenticationContext = new AuthenticationContext(strAutherizationString + strDirectoryId, false);
try
{
if (TokenForUser == null)
{
ClientCredential clientCred = new ClientCredential(strClientId, strSecretKey);
AuthenticationResult userAuthnResult = await authenticationContext.AcquireTokenAsync(strResourceURL, clientCred);
TokenForUser = userAuthnResult.AccessToken;
//Capturing the token for the user
log.Info("GetTokenForUser()" + TokenForUser);
}
return TokenForUser;
}
catch (Exception ex)
{
//Capturing the error log for the user token
log.Error("GetTokenForUser()", ex);
throw ex;
}
finally
{
authenticationContext.TokenCache.Clear();
}
}
Can anyone help me out?

Resources