I am trying to login to Azure with my c# code and was able to find something like this on the net.
Not sure what to pass in the scope array here and once I get the access token, how can I make the rest call ?
public static string getAccessToken(string[] scopes)
{
var interactiveCredential = new InteractiveBrowserCredential();
return interactiveCredential.GetToken(new Azure.Core.TokenRequestContext(scopes, null)).Token;
}
First create a Azure AD Application:
Follow this link
Then get the Tenant ID , Client ID , Client Secret from the AD Application and use this class to query your azure subscription recourses.
class CustomLoginCredentials : ServiceClientCredentials
{
//Variables
private static string tenantId = "<Tenant ID goes here>";
private static string clientId = "<Client ID goes here>";
private static string clientSecret = "<Client Secret goes here>";
private static string windowsURL = "https://login.windows.net/";
private static string azureManagementURL = "https://management.azure.com/";
private string AuthenticationToken { get; set; }
public override void InitializeServiceClient<T>(ServiceClient<T> client)
{
var authenticationContext =
new AuthenticationContext(windowsURL + tenantId);
var credential = new ClientCredential(clientId, clientSecret);
var result = authenticationContext.AcquireTokenAsync(azureManagementURL,
clientCredential: credential).Result;
AuthenticationToken = result.AccessToken;
}
public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationToken);
await base.ProcessHttpRequestAsync(request, cancellationToken);
}
}
Example:
private async void GetAzureResourcesConsumption()
{
var credentials = new CustomLoginCredentials();
ConsumptionManagementClient client = new ConsumptionManagementClient(credentials);
client.SubscriptionId = subscriptionId;
var resources = await client.UsageDetails.ListAsync(null, null, null, top: NumberOfItems);
var results = resources.ToList<UsageDetail>();
}
Do you mean to get access token?
private static string GetAuthorizationToken()
{
ClientCredential cc = new ClientCredential(ClientId, ServicePrincipalPassword);
var context = new AuthenticationContext("https://login.windows.net/" + AzureTenantId);
var result = context.AcquireTokenAsync("https://management.azure.com/", cc);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
return result.Result.AccessToken;
}
Related
I have a class that acts as a wrapper for the MS Graph SDK. Fairly simple purpose, inside the class there are methods for getting various data sets out of Graph for a particular user.
EDIT: this runs under the context of an application, so no user creds are ever used.
All of that part works fine, what isn't working is the DelegateAuthenticationProvider never finds the access token in the cache. Each call to a graph endpoint gets a new token, even in the same instance of the class. Within the class I'm using a singleton pattern for the GraphServiceClient.
Here is the code I'm using to handle the client:
private static GraphServiceClient _graphServiceClient;
private static AuthenticationContext _authContext;
private static readonly object _locker = new();
private GraphServiceClient GetClient(M365ServiceOptions options)
{
if (_graphServiceClient == null)
{
lock (_locker)
{
if (_graphServiceClient == null)
{
_authContext = new AuthenticationContext($"https://login.microsoftonline.com/{options.TenantId}/");
var provider = new DelegateAuthenticationProvider(async (requestMessage) =>
{
AuthenticationResult accessToken;
try
{
//Use Token from cache or refresh token
accessToken = await _authContext.AcquireTokenSilentAsync(options.GraphURL, options.ClientId);
_logger.LogDebug("Cache Hit");
}
catch (AdalSilentTokenAcquisitionException)
{
//If no cached token, get a new one
_logger.LogDebug($"Cache Miss: {_authContext.TokenCache?.Count}");
var credentials = new ClientCredential(options.ClientId, options.ClientSecret);
accessToken = _authContext.AcquireTokenAsync(options.GraphURL, credentials).Result;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken);
});
_graphServiceClient = new GraphServiceClient(provider);
}
}
}
return _graphServiceClient;
}
While debugging it is clear the token cache has an item in it, and the details all seem to match, but no matter what, the AcquireTokenSilentAsync always throws the AdalSilentTokenAcquisitionException exception and forces it to get a new token for each call. This is impacting performance as no matter what, each call to the graph gets a new token.
Thank you for any assistance.
Please try this class. First call AuthenticationHelper.GetAuthenticatedClient() to get the a GraphServiceClient, then you this to access the user information.
public class AuthenticationHelper
{
static readonly string clientId = "";
public static string[] Scopes = { "User.Read" };
public static PublicClientApplication IdentityClientApp = new PublicClientApplication(clientId);
public static string TokenForUser = null;
public static DateTimeOffset Expiration;
private static GraphServiceClient graphClient = null;
// Get an access token for the given context and resourced. An attempt is first made to
// acquire the token silently. If that fails, then we try to acquire the token by prompting the user.
public static GraphServiceClient GetAuthenticatedClient()
{
if (graphClient == null)
{
// Create Microsoft Graph client.
try
{
graphClient = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var token = await GetTokenForUserAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
requestMessage.Headers.Add("SampleID", "MSGraphConsoleApp");
}));
return graphClient;
}
catch (Exception ex)
{
Debug.WriteLine("Could not create a graph client: " + ex.Message);
}
}
return graphClient;
}
public static async Task<string> GetTokenForUserAsync()
{
AuthenticationResult authResult;
try
{
authResult = await IdentityClientApp.AcquireTokenSilentAsync(Scopes, IdentityClientApp.GetAccountsAsync().Result.First());
TokenForUser = authResult.AccessToken;
}
catch (Exception)
{
if (TokenForUser == null || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
authResult = await IdentityClientApp.AcquireTokenAsync(Scopes);
TokenForUser = authResult.AccessToken;
Expiration = authResult.ExpiresOn;
}
}
return TokenForUser;
}
public static void SignOut()
{
foreach (var user in IdentityClientApp.GetAccountsAsync().Result)
{
IdentityClientApp.RemoveAsync(user);
}
graphClient = null;
TokenForUser = null;
}
}
I am trying to create a C# version of a JavaScript Amazon Cognito user pool authentication (see here) but it does not work. The response always shows null. Please find code below:
using System;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
namespace ConsoleApp1
{
class AmazonCognitoSetup
{
private AuthFlowResponse response;
public AuthFlowResponse Response { get; set; }
public async void AsyncStuff()
{
String userpool_id = "us-west-2_NqkuZcXQY";
String client_id = "4l9rvl4mv5es1eep1qe97cautn";
String username = "username"
String password = "password"
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USWest2);
var userpool = new CognitoUserPool(userpool_id, client_id, provider);
var user = new CognitoUser(username, client_id, userpool, provider);
InitiateSrpAuthRequest initiateSrpAuthRequest = new() { Password = password};
Console.WriteLine("Getting credentials");
response = await user.StartWithSrpAuthAsync(initiateSrpAuthRequest).ConfigureAwait(false);//shows null
var accesstoken = response.AuthenticationResult.AccessToken;
Console.WriteLine(accesstoken);
}
}
}
Fixed it. The issue was that the authentication was asynchronous so I had to find a way to block until the response came back. See redone code below:
using System;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class AmazonCognitoSetup
{
private string userpool_id = "us-west-2_NqkuZcXQY";
private string client_id = "4l9rvl4mv5es1eep1qe97cautn";
private string username = "username";
private string password = "password";
private string idToken;
private string refreshToken;
private string accessToken;
public string IdToken { get => idToken; set => idToken = value; }
public string RefreshToken { get => refreshToken; set => refreshToken = value; }
public string AccessToken { get => accessToken; set => accessToken = value; }
public void AsyncStuff()
{
//FileMaker PRO credentials for Amazon
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USWest2);
var userpool = new CognitoUserPool(userpool_id, client_id, provider);
var user = new CognitoUser(username, client_id, userpool, provider);
InitiateSrpAuthRequest initiateSrpAuthRequest = new() {
Password = password
};
//authenticate to get tokens <--- change was here
var task = Task.Run<AuthFlowResponse>(async()=> await user.StartWithSrpAuthAsync(initiateSrpAuthRequest));
//assign tokens from results
this.idToken = task.Result.AuthenticationResult.IdToken;
this.refreshToken = task.Result.AuthenticationResult.RefreshToken;
this.accessToken = task.Result.AuthenticationResult.AccessToken;
}
}
}
Ok, the second pair of eyes time for some reason my property is always coming back null.
They keys described here are only for demo purchases and will not work other wise.
public class RoundTableAPIClient {
public string ApiKey { get; set; }
public string ClientSecret { get; set; }
}
This is a class that I store all my API calls in
private readonly HttpClient _httpClient;
public RoundTableAPIClient() {
_httpClient = new HttpClient();
if (ApiKey != null | ClientSecret != null) {
_httpClient.DefaultRequestHeaders.Add(Constants.ApiKey, ApiKey);
_httpClient.DefaultRequestHeaders.Add(Constants.ClientSecret, ClientSecret);
}
}
The values for ApiKey and Client secret are null in my stock controller I am passing them in
public class StockController : Controller {
private readonly IStringLocalizer<StockController> _localizer;
RoundTableAPIClient apiClient;
public StockController(IStringLocalizer<StockController> localizer) {
_localizer = localizer;
apiClient = new RoundTableAPIClient();
}
This is my get example where I am going to the api to get the data this function is contained withing my stock controller.
public async Task<object> Get(DataSourceLoadOptions loadOptions) {
List<Stock> _result = new List<Stock>();
apiClient.DeveiceType = device.Desktop;
apiClient.DeveiceType = device.Desktop;
apiClient.ApiKey = "B538F53B-37F7-4564-B7C5-56AFF399252B";
apiClient.ClientSecret = "8132ED0B-8F0B-4841-8BF4-CE8438AC0F3E";
_result = await apiClient.GetStockFromApi();
return DataSourceLoader.Load(_result, loadOptions);
}
public async Task<List<Stock>> GetStockFromApi() {
List<Stock> _result = new List<Stock>();
var uri = new Uri(string.Format(ApiUrl + Constants.GetALlStock, string.Empty));
var response = await _httpClient.GetAsync(uri);
if (response.IsSuccessStatusCode) {
var byteArray = await response.Content.ReadAsByteArrayAsync();
var content = Encoding.UTF8.GetString(byteArray, 0, byteArray.Length);
_result = JsonConvert.DeserializeObject<List<Stock>>(content);
}
return _result.ToList();
}
Its here when I inspect my http client that the default headers are still bank I dont understand why that is the case.
Edit 2
Should I be doing it more like this?
public async Task<List<Stock>> GetStockFromApi(string ApiKey,string ClientSecret) {
List<Stock> _result = new List<Stock>();
var uri = new Uri(string.Format(ApiUrl + Constants.GetALlStock, string.Empty));
var response = await _httpClient.GetAsync(uri);
if (ApiKey != null | ClientSecret != null) {
_httpClient.DefaultRequestHeaders.Add(Constants.ApiKey, ApiKey);
_httpClient.DefaultRequestHeaders.Add(Constants.ClientSecret, ClientSecret);
}
if (response.IsSuccessStatusCode)
{
var byteArray = await response.Content.ReadAsByteArrayAsync();
var content = Encoding.UTF8.GetString(byteArray, 0, byteArray.Length);
_result = JsonConvert.DeserializeObject<List<Stock>>(content);
}
return _result.ToList();
}
You add the headers in the constructor on the condition that either ApiKey or ClientSecret is not null, but they will always be null at that stage. You probably want to give those default values, or add them as parameters in the constructor.
How to refresh Authentication token for
Microsoft Graph using Microsoft Graph .NET Client Library or other using C#?
What I am currently doing is keeping token in the static class:
public class TokenKeeper
{
public static string token = null;
public static string AcquireToken()
{
if (token == null || token.IsEmpty())
{
throw new Exception("Authorization Required.");
}
return token;
}
public static void Clear()
{
token = null;
}
}
I fill in the token in Startup class:
public partial class Startup
{
private static string AppKey = CloudConfigurationManager.GetSetting("ida:Password");
private static string aadInstance = CloudConfigurationManager.GetSetting("ida:AADInstance");
private static string TenantName = CloudConfigurationManager.GetSetting("ida:Tenant");
private static string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, TenantName);
private static string graphResourceId = CloudConfigurationManager.GetSetting("ida:GraphUrl");
private BpContext db = new BpContext();
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
string ClientId = CloudConfigurationManager.GetSetting("ida:ClientID");
string Authority = "https://login.microsoftonline.com/common/";
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
Scope = "User.ReadBasic.All",
//Details omitted
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
// Create a Client Credential Using an Application Key
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));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
TokenKeeper.token = result.AccessToken;
return Task.FromResult(0);
}
//Details omitted
}
});
}
}
I also clear the token on Sign Out.
The AuthenticationResult object contains both access token and refresh token. So, the refresh token can also be persisted in TokenKeeper similar to access token. When access token expires (indicated by AuthenticationResult.ExpiresOn), use the refresh token with AuthenticationContext.AcquireTokenByRefreshToken method to get new access token.
If you don't want to track refresh tokens explicitly, please refer to ADAL Cache to know how ADAL library can do it for you.
You can refresh access token by providing RefreshToken which you received alongside AccessToken. Since you have ID/Secret available in you code you can use them to provide ClientCredential.
Code example would be:
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var result = authContext.AcquireTokenByRefreshToken(refreshToken, new ClientCredential(ClientId, AppKey));
I'm quite new to Web API and i still don't get it how to request n JWT.
In My Startup.cs i configured OAuth and my custom JWT:
CustomJwtFormat.cs
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string issuer = string.Empty;
private readonly int timeoutMinutes = 60;
public CustomJwtFormat(string issuer)
{
this.issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audience = "all";
var secret = Convert.FromBase64String("mySecret");
var now = DateTime.UtcNow;
var expires = now.AddMinutes(timeoutMinutes);
string signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256";
string digestAlgorithm = "http://www.w3.org/2001/04/xmlenc#sha256";
var signingKey = new SigningCredentials(
new InMemorySymmetricSecurityKey(secret), signatureAlgorithm, digestAlgorithm);
var token = new JwtSecurityToken(issuer, audience, data.Identity.Claims,
now, expires, signingKey);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return tokenString;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
EDIT: I managed retrieving a token with Postman.
Now my next problem: as long as i send a valid jwt everything works ok. But whenn i send e.g. 'bearer 12345' i get the error message
IDX10708: 'System.IdentityModel.Tokens.JwtSecurityTokenHandler' cannot read this string: '12345'.
...in Visual Studio. Where do i need to put in the token validation? Make a new class and put the TokenSecurityHandler anywhere?