microsoft-identity-web: [Bug] Calling TokenAcquisition.GetAccessTokenForUserAsync throws a NullReferenceException
Which version of Microsoft Identity Web are you using? 1.16.1
Where is the issue?
- Web app
- Sign-in users
- Sign-in users and call web APIs
- Web API
- Protected web APIs (validating tokens)
- Protected web APIs (validating scopes)
- Protected web APIs call downstream web APIs
- Token cache serialization
- In-memory caches
- Session caches
- Distributed caches
- Other (please describe)
Is this a new or an existing app? The app is in production and I have upgraded to a new version of Microsoft Identity Web. This error started happening after upgrading from 1.15.2 to 1.16.1. I’ve verified this by downgrading the package to 1.15.2 again.
I think maybe this issue is related to #1372 but it has already been closed so I opened a new issue for this.
Repro
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(azureAdSection, cookieScheme: null)
.EnableTokenAcquisitionToCallDownstreamApi().AddInMemoryTokenCaches();
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
}
}
public class BaseController : Controller
{
private readonly SignInManager<User> _signInManager;
private readonly ITokenAcquisition _tokenAcquisition;
private readonly IGraphApiClient _graphApiClient;
public BaseController(
SignInManager<User> signInManager,
ITokenAcquisition tokenAcquisition,
IGraphApiClient graphApiClient
)
{
_signInManager = signInManager;
_tokenAcquisition = tokenAcquisition;
_graphApiClient = graphApiClient;
}
[Route("ExternalLoginCallback")]
public async Task<IActionResult> ExternalLoginCallback(
string returnUrl,
string? remoteError = null
)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(
new[] { "User.Read" },
user: info.Principal
);
}
}
Expected behavior
I expected ITokenAcquisition.GetAccessTokenForUserAsync()
to return an access token like it did in 1.15.2 instead of throwing a NullReferenceException.
Actual behavior
The call to ITokenAcquisition.GetAccessTokenForUserAsync()
throws the following exception:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(MergedOptions mergedOptions)
at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(MergedOptions mergedOptions)
at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
at Microsoft.Identity.Web.TokenAcquisition.GetAccessTokenForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
at TEST.Web.Controllers.BaseController.ExternalLoginCallback(String returnUrl, String remoteError) in D:\Code\DotNet\Test\TEST.Web\Controllers\BaseController.cs:line 159
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.ResponseCaching.ResponseCachingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 20
I solved this problem by implementing custom AuthenticationSchemeProvider according to this thread: https://stackoverflow.com/questions/46464469/how-to-configureservices-authentication-based-on-routes-in-asp-net-core-2-0
After having updated from 1.15.2 to 1.24.1 I now have to pass
OpenIdConnectDefaults.AuthenticationScheme
as theauthenticationScheme
parameter toGetAccessTokenForUserAsync
for this to work otherwise it throws this new exception the PR added:Is this a required parameter now or is this a bug? The documentation for the parameter says “Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme if called from a web app…” so I thought it should work without passing a scheme to it? There’s also a spelling mistake there as it should say
OpenIdConnectDefaults.AuthenticationScheme
and notOpenIdConnectDefault.AuthenticationScheme
.I’m not sure if this matters but I’m not passing OpenIdConnect as the default authentication scheme when I configure authentication because I’m using both cookie and OpenID Connect authentication:
See PR: https://github.com/AzureAD/microsoft-identity-web/pull/1672
@jennyf19 I’ve written a repro you can run locally here now: https://github.com/davidkarlsson/identity-web-token-issue.