Refresh auth token for MS Graph with C# - c#

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));

Related

Setting up authentication endpoints class in minimal API

In Program.cs I have a post endpoint for getting a Jwt token:
app.MapPost("/api/security/getToken", [AllowAnonymous] async (UserManager<IdentityUser> userMgr, UserLoginDTO user) => {
var identityUser = await userMgr.FindByEmailAsync(user.Email);
if (await userMgr.CheckPasswordAsync(identityUser, user.Password)) {
var issuer = builder.Configuration["Jwt:Issuer"];
var audience = builder.Configuration["Jwt:Audience"];
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim> {
new Claim(ClaimTypes.Email, identityUser.Email),
new Claim(ClaimTypes.GivenName, identityUser.UserName)
};;
var token = new JwtSecurityToken(issuer: issuer, audience: audience, signingCredentials: credentials, claims: claims);
var tokenHandler = new JwtSecurityTokenHandler();
var stringToken = tokenHandler.WriteToken(token);
return Results.Ok(stringToken);
}
else {
return Results.Unauthorized();
}
}).WithTags("Security");
I want to move that logic in a separate class for readability.
public static class SecurityEndpoints {
public static void MapSecurityEndpoints(this WebApplication app) {
app.MapPost("/api/security/getToken", GetToken).AllowAnonymous();
app.MapPost("/api/security/createUser", CreateUser).AllowAnonymous();
}
internal static async Task<IResult> GetToken(this WebApplicationBuilder builder, UserManager<IdentityUser> userMgr,[FromBody] UserLoginDTO user) {
//same logic
}
...
}
Because of the WebApplicationBuilder I'm getting a InvalidOperationException.
How to refactor the GetToken method to get rid of WebApplicationBuilder and maybe access the builder.Configuration details that I need form another service? How would that service look like?
Or how else would you approach this?
You should not use WebApplication(Builder) in your handlers. Use standard approaches for working with the configuration in .NET/ASP.NET Core which are described here.
For example create type for jwt settings, register it with options pattern and inject into handler. Something along this lines:
public class JwtOptions
{
public const string Position = "Jwt";
public string Issuer { get; set; } = String.Empty;
public string Audience { get; set; } = String.Empty;
}
builder.Services.Configure<JwtOptions>(
builder.Configuration.GetSection(JwtOptions.Position));
internal static async Task<IResult> GetToken(IOptions<JwtOptions> jwtOptions,...) {
// use jwtOptions
}

Rest Call to Azure

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;
}

MSAL for C# with different directory/user guid

Currently I'm implementing Microsoft Authentication Library(MSAL) on my C# .NET framework webapp (single tenant) and when I acquire the token using the code from Owin I'm getting the wrong GUID for the user/tenant in my confidential app.
This is the return from the confidential app(dc3... is the UserId and cf3.. is the TenantId), this is from a different directory on Azure.
But the claims generated by C# have the correct values:
If I check the object from the confidential app I can see inside "TenantProfiles" the same values as the above (f81 and e24), the correct ones.
But since the Claims have different values as the Confidential App, I cannot get the user with GetAccountAsync(), because it tries to find a user based on "dc3" GUID not "f81" GUID. I can get the user using a filter on GetAccountsAsync(), but this method is deprecated.
Here's my code
public static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"]; //https://login.microsoftonline.com/{0}
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant) + "/v2.0";
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() { CookieSecure = CookieSecureOption.SameAsRequest });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = Startup.Authority,
ClientId = Startup.clientId,
RedirectUri = Startup.redirectUri,
PostLogoutRedirectUri = Startup.redirectUri,
Scope = OpenIdConnectScopes.OpenIdProfile,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthorizationFailed
}
});
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
var app = IdentityApiUtility.BuildConfidentialClientApplication();
var result = await app.AcquireTokenByAuthorizationCode(new[] { "https://graph.microsoft.com/.default" }, notification.Code).ExecuteAsync();
}
and
public static IConfidentialClientApplication BuildConfidentialClientApplication()
{
if (clientapp == null)
{
clientapp = ConfidentialClientApplicationBuilder
.Create(Startup.clientId)
.WithClientSecret(Startup.appKey)
.WithRedirectUri(Startup.redirectUri)
.WithAuthority(Startup.Authority)
.Build();
}
return clientapp;
}
/// <summary>
/// Gets an auth code on behalf of the current user
/// </summary>
private AuthenticationResult GetOpenIdConnectAuth()
{
try
{
string userObjectID = $"{ClaimsPrincipal.Current.GetObjectId()}.{ClaimsPrincipal.Current.GetTenantId()}";
var app = BuildConfidentialClientApplication();
var scopes = new[] { "https://graph.microsoft.com/.default" };
//The userObjectId here starts with f81, which I got from the claims. But the user in the ConfidentialApp starts with dc3 which from another Azure Directory
var account = app.GetAccountAsync(userObjectID).Result;
var accessToken = app.AcquireTokenSilent(scopes, account).ExecuteAsync().Result;
return accessToken;
}
catch (Exception ex)
{
throw new Exception("Authentication Error in GetOpenIdConnectAuth method");
}
}
I already checked clientid/secret/tenant multiple times just to be sure that I wasn't sending the wrong authority/tenant and this is not the case. Does anyone have a suggestion how I can get the user from the ConfidentialApp or what I'm doing wrong?

Azure Marketplace Fulfillment API throws Error 403 (.NET Framework)

I try to integrate with Azure Marketplace. As a first step I try to implement Resolve API request.
I used a commercial-marketplace-client-dotnet GitHub repository as an example.
Our solution uses .NET Framework stack and I implemented some changes in the GitHub sources.
I have migrated class ClientSecretTokenProvider from .NET Core to .NET Framework.
My implementation of ClientSecretTokenProvider is here:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Rest;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace CloudMonix.Infrastructure.AzureMarketplace
{
public class ClientSecretTokenProvider : ITokenProvider
{
// https://learn.microsoft.com/en-us/azure/marketplace/partner-center-portal/pc-saas-registration#get-a-token-based-on-the-azure-ad-app
private const string AuthenticationEndpoint = "https://login.microsoftonline.com/{0}/oauth2/token";
private const string MarketplaceResourceId = "20e940b3-4c77-4b0b-9a53-9e16a1b010a7";
private readonly string _tenantId;
private readonly string _clientId;
private readonly string _clientSecret;
public ClientSecretTokenProvider(string tenantId, string clientId, string clientSecret)
{
_tenantId = tenantId;
_clientId = clientId;
_clientSecret = clientSecret;
}
public Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
{
var credential = new ClientCredential(_clientId, _clientSecret);
var authority = string.Format(AuthenticationEndpoint, _tenantId);
var scope = $"{MarketplaceResourceId}/.default";
var context = new AuthenticationContext(authority);
var token = context.AcquireToken(scope, credential);
var result = new AuthenticationHeaderValue("Bearer", token.AccessToken);
return Task.FromResult(result);
}
}
}
All other classes (auto-generated client and models) I have added to our solution without any changes.
Also I have registered new Azure Application (and generate Client Secret Key). I provided Application ID and Tennant ID to my offer:
I can receive Azure Marketplace Token from the Landing Page request GET parameter.
Also I can successfully get AccessToken using ClientSecretTokenProvider class.
But when I use AccessToken to the Fulfillment API (Resolve API request) then I got Error 403.
What I'm missing?
Thanks to advance to any help.
UPDATE 1:
Example of usage MarketplaceSaaSClient class:
public ActionResult Index(string token)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
if (!string.IsNullOrEmpty(token))
{
var tenantId = "<TENANT ID>";
var clientId = "<APP ID>";
var clientSecret = "<APP SECRET>";
var requestId = Guid.NewGuid();
var correlationId = Guid.NewGuid();
var client = new MarketplaceSaaSClient(tenantId, clientId, clientSecret);
var subscription = client.FulfillmentOperations.Resolve(token, requestId, correlationId);
}
ViewBag.Title = "Home Page";
return View();
}

Azure AAD ClaimsPrincipal IsInRole always returns false

I'm having an issue using Azure AAD appRoles and MVC, i have modified the manifest added a few roles and assigned them to a couple of users.
However when i try using either User.IsInRole or ClaimsPrincipal.Current.IsInRole it always returns false.
Click Here to see
The role is being return in the json of Claims in the screenshot above {roles:SuperAdmin}.
I have done alot of reading up and as far as i can see i am doing everything correctly but cant find a reason why?
Below is my Startup.Auth.cs
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
public static readonly string Authority = aadInstance + tenantId;
// This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API.
//string graphResourceId = "https://graph.windows.net";
public void ConfigureAuth(IAppBuilder app)
{
ApplicationDbContext db = new ApplicationDbContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType= "roles"
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
return Task.FromResult(0);
}
}
});
}
}
Since you are using OpenID Connect Owin middleware to sign-in users from Azure AD , you doesn't need to enable App Service Authentication / Authorization feature , that feature provides a way for your application to sign in users so that you don't have to change code on the app backend. Just turn off the App Service Authentication / Authorization feature .

Categories

Resources