[Bug] : NullReferenceException when acquiring a token for a user in a server-side Blazor app.
See original GitHub issueWhich Version of Microsoft Identity Web are you using ? Microsoft Identity Web 0.1.2-preview
Where is the issue?
- Web App
- Sign-in users
- [ X] 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
- [X ] In Memory caches
- Session caches
- Distributed caches
This is a new application that is a server-side Razor application calling a protected Web API. The application works as expected locally, but fails with a NullReferenceException
when running as an Azure App Service. The failure occurs when trying to retrieve a token for a user (GetAccessTokenForUserAsync
). The request to get a token for the user occurs in a lower-level HttpClient service when retrieving the token to add as an Authorization header. The HttpClient service is injected into the Blazor page (@inject). As a test, I’ve added code to the OnGet() method of the Blazor page (which runs on first access of a page from the site), and the same code works as expected both locally and in Azure (GetAccessTokenForUserAsync
returns a valid token with no exception).
I have confirmed that the following values are correctly configured:
- TenantId
- ClientId
- ClientSecret
I have confirmed the application has been registered correctly in Azure AD with valid value(s) for:
- RedirectURIs
- Scopes
Given I can run the application locally with no exceptions, I don’t think I have missing or invalid configuration. I also don’t think this issue has anything to do with the Web API itself, this exception occurs simply trying to get a token from the cache and/or from MSAL prior to communication with the (protected) Web API.
Repro
Startup.cs
services
.AddSignIn(
openIdOptions => Configuration.Bind("AppAzureAd", openIdOptions),
identityOptions => Configuration.Bind("AppAzureAd", identityOptions)
)
.AddWebAppCallsProtectedWebApi(
Configuration.GetSection("Api:Scopes").Get<List<string>>(),
openIdOptions => Configuration.Bind("AppAzureAd", openIdOptions),
identityOptions =>
{
Configuration.Bind("AppAzureAd", identityOptions);
identityOptions.EnablePiiLogging = environment.IsDevelopment();
}
)
.AddInMemoryTokenCaches()
.AddTokenAcquisition();
Blazor Page method that works as expected (no exceptions, token acquired) on first page request:
public async Task<IActionResult> OnGet()
{
if (!User.Identity.IsAuthenticated)
return Challenge();
try
{
var token = await tokenAcquisition.GetAccessTokenForUserAsync(apiOptions.Value.Scopes);
}
catch (MsalUiRequiredException ex)
{
//can't get a token from the token store, MUST assume a sign-out path as requests to API will NOT be authenticated
logger.LogError(ex, ex.Message);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Challenge();
}
return Page();
}
HttpClient setting the authorization header (GetAccessTokenForUserAsync
works locally, but fails in Azure):
private async Task AddAuthorizationHeaderAsync(HttpRequestMessage request)
{
request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, await AcquireTokenAsync());
}
private async Task<string> AcquireTokenAsync()
{
string token;
try
{
token = await tokenAcquisition.GetAccessTokenForUserAsync(apiOptions.Value.Scopes);
}
catch (MsalUiRequiredException ex)
{
//can't get a token from the token store, MUST assume a sign-out path as requests to API will NOT be authenticated
logger.LogError(ex, ex.Message);
throw;
}
return token;
}
Expected behavior I expect to be able to acquire a valid token for the user at any point during the “request” lifecycle.
Actual behavior The following stack trace:
[2020-05-12T21:55:47.258Z] Error: System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Identity.Web.TokenAcquisition.CreateRedirectUri()
at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplicationAsync()
at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplicationAsync()
at Microsoft.Identity.Web.TokenAcquisition.GetAccessTokenForUserAsync(IEnumerable`1 scopes, String tenant)
...
Possible Solution At a minimum, better exception handling that leads me to the actual error.
Issue Analytics
- State:
- Created 3 years ago
- Comments:25 (2 by maintainers)
Top GitHub Comments
@jennyf19 sounds good to me! Thx for the support 💪🏻
@gwgrubbs & @schmid37 glad to hear it’s working, thanks for confirming. we are aware of the redirect issue, it’s because blazor doesn’t have the challenge method, so we’re trying to come up with a work around.