Reputation: 351
I'm new to multi-tenant apps and have been googling for a couple days on how to get the access_token natively in .NET Core 2.1. So far everything I've found is 2.0 or earlier and none of the methods posted even exist in 2.1.
I've create a Microsoft Graph helper that takes a string access_token
and will fetch the user details. I'm simply trying to invoke the helper with an access_token
after receiving the authorization code (OnAuthorizationCodeReceived event).
I feel like this should be a one-liner or short snippet at most, and I just can't seem to find a solution.
Here's my Azure AD extension where I'm wanting this to occur:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Authentication
{
public static class AzureAdAuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
=> builder.AddAzureAd(_ => { });
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect();
return builder;
}
private class ConfigureAzureOptions: IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly AzureAdOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
{
_azureOptions = azureOptions.Value;
}
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Authority = $"{_azureOptions.Instance}";
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.IssuerValidator = ValidateIssuer;
options.Events.OnAuthenticationFailed = AuthenticationFailed;
options.Events.OnAuthorizationCodeReceived = AuthorizationCodeReceived;
}
// TODO check tenant against database for authorized tenants
private string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
if (false)
{
//throw new SecurityTokenInvalidIssuerException();
// how do i get my db context here if there's no context in the scope?
// var db = context.HttpContext.RequestServices.GetRequiredService<RdmsContext>(); <-- something like this
}
// allowed
return issuer;
}
private static Task AuthenticationFailed(
AuthenticationFailedContext context)
{
context.HandleResponse();
string message = Uri.EscapeUriString(context.Exception.Message);
context.Response.Redirect($"/Home/Error?message={message}");
return Task.CompletedTask;
}
private static async Task AuthorizationCodeReceived(
AuthorizationCodeReceivedContext context)
{
string authorizationCode = context.ProtocolMessage.Code;
string idToken = context.ProtocolMessage.IdToken;
// ProtocolMessage has AccessToken property, but it's null.
// Exchange authorization code for access_token here
string accessToken = ...
var userDetails = MyProject.Helpers.Graph
.GetUserDetailsAsync(accessToken);
context.HandleCodeRedemption(accessToken, idToken);
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
}
}
}
Upvotes: 1
Views: 1873
Reputation: 325
Thank you so much, I was wondering how to get access_token for microsoft graph but i'm using Microsoft.AspNetCore.Authentication.AzureAD.UI (which abstracts all the OpenId configuration) and i think that options.UseTokenLifetime = true
and options.SaveTokens = true;
helped to access to context.ProtocolMessage.AccessToken
Thank you again
Upvotes: 0
Reputation: 351
After lots of trial and error, I finally figured out the missing pieces.
First, I changed options.ResponseType
to OpenIdConnectResponseType.IdTokenToken
which as I understand it returns an IdToken as well as a Token (access token). This requires supplying a resource for which the access token will be used.
So, I've also added options.Resource
with a value of "https://graph.microsoft.com"
.
I also removed options.GetClaimsFromUserInfoEndpoint = true;
I also had to update the application's manifest in Azure to change oauth2AllowImplicitFlow
to true
.
Finally I replaced the OnAuthorizationCodeRecevied
event with OnTokenValidated
as the point from which I call my Microsoft Graph helper.
This combination of changes resulted in successfully receiving the access token which I could then feed to my Microsoft Graph helper and get what I need.
The final Configure
method now looks like this:
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Resource = "https://graph.microsoft.com";
options.Authority = $"{_azureOptions.Instance}";
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.IdTokenToken;
options.SaveTokens = true;
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.IssuerValidator = ValidateIssuer;
options.Events.OnAuthenticationFailed = AuthenticationFailed;
options.Events.OnTokenValidated = TokenValidatedAsync;
}
And TokenValidatedAsync
now has the access token found in TokenValidatedContext.ProtocolMessage.AccessToken
:
private static async Task TokenValidatedAsync(
TokenValidatedContext context)
{
string accessToken = context.ProtocolMessage.AccessToken;
Graph.User userDetails = await MyProject.Helpers.Graph
.GetUserDetailsAsync(accessToken);
}
From here I can do what I need with the Microsoft Graph user details.
I couldn't find any working example of this so I'll leave this here for future reference.
Upvotes: 3