question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Bug] ConsentHandler in MVC Partial View Produces Malformed Redirect Uri

See original GitHub issue

Which version of Microsoft Identity Web are you using? Microsoft Identity Web 1.0.0

Where is the issue?

  • Web app
    • Sign-in users and call web APIs
  • Token cache serialization
    • Distributed caches

Is this a new or an existing app? c. This is a new app or an experiment.

Repro

StartUp.cs

services.Configure<AuthenticationOptions>(Configuration.GetSection("AzureAd"));
string[] initialScopes = Configuration.GetSection("ServerRequestApi:Scopes").Get<string[]>();

services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
    .AddDistributedTokenCaches();

services.TryAddScoped<MicrosoftIdentityConsentAndConditionalAccessHandler>();

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";
});

services.AddHttpClient<IApiHealthService, ApiHealthService>();

services.AddHttpContextAccessor();

services.AddControllersWithViews(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();

ApiHealthService.cs

[AuthorizeForScopes(ScopeKeySection = "ServerRequestApi:Scopes)")]
public class ApiHealthService : IApiHealthService
{
    private readonly HttpClient _apiClient;
    private readonly ILogger<ApiHealthService> _logger;
    private readonly IConfiguration _configuration;
    private readonly ITokenAcquisition _tokenAcquisition;
    private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;

    private readonly string _remoteServiceBaseUrl;
    private readonly string[] _scopes;

    public ApiHealthService(
        HttpClient httpClient,
        ILogger<ApiHealthService> logger,
        IConfiguration configuration,
        ITokenAcquisition tokenAcquisition,
        MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
        _apiClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        _tokenAcquisition = tokenAcquisition ?? throw new ArgumentNullException(nameof(tokenAcquisition));
        _consentHandler = consentHandler ?? throw new ArgumentNullException(nameof(consentHandler));

        _remoteServiceBaseUrl = _configuration.GetSection("ServerRequestApi").GetValue<string>("ApiEndpoint");
        _scopes = _configuration.GetSection("ServerRequestApi:Scopes").Get<string[]>();
            
    }

    public async Task AuthorizeApiClient()
    {
        string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(_scopes).ConfigureAwait(false);

        _apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    }

    public async Task<IEnumerable<PipelineStatus>> GetPipelineStatus(bool showInactive)
    {
        await AuthorizeApiClient();
        List<PipelineStatus> parsedPipelineStatus = null;
        try
        {
            var jsonPipelineStatus = await _apiClient.GetStringAsync($"{_remoteServiceBaseUrl}Health/GetPipelineStatus?showInactive={showInactive}");
            parsedPipelineStatus = JsonConvert.DeserializeObject<List<PipelineStatus>>(jsonPipelineStatus);
        }
        catch (Exception ex)
        {
            _consentHandler.HandleException(ex);
        }
            
        if (null == parsedPipelineStatus)
        {
            return new List<PipelineStatus>();
        }
        parsedPipelineStatus = parsedPipelineStatus.ToList();
        return parsedPipelineStatus;
    }
}

_Layout.cshtml

@inject IApiHealthService HealthSvc
@inject Microsoft.Identity.Web.MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
<...>
<div class="container body-content">
        @{
            try
            {
                var pipelineStatus = await HealthSvc.GetPipelineStatus(false);
                if (pipelineStatus.Any())
                {
                    foreach (var message in pipelineStatus)
                    {
                        <div class="alert alert-warning">
                            @message.Timestamp: @message.Message
                        </div>
                    }
                }
            }
            catch (Exception ex)
            {
                ConsentHandler.HandleException(ex);
            }
        }
        @RenderBody()
    </div>

Expected behavior The user is redirected to the expected page.

Actual behavior The redirect Uri returned by MicrosoftIdentityConsentAndConditionalAccessHandler.HandleException (non-Blazor) is malformed, containing and extra /. E.g.: Attempts to navigate to https://localhost:37289 redirects to https://localhost:37289//, navigating to https://localhost:37289/VMRequests redirects to https://localhost:37289//VMRequests/

To recreate:

  1. log in to the app normally (navigate to https://localhost:37289)
  2. delete the token from the cache
  3. force a refresh while still on the main page (https://localhost:37289)
  4. be redirected to https://localhost:37289// (you can see the request in the debug console. E.g.: https://localhost:37289/MicrosoftIdentity/Account/Challenge?redirectUri=https://localhost:37289//&scope=...)

This also works with an InMemoryTokenCache if you leave the browser open but restart the app.

Possible solution Modify MicrosoftIdentityConsentAndConditionalAccessHandler.HandleException or MicrosoftIdentityConsentAndConditionalAccessHandler.CreateBaseUri to not add in extra /s if they were not present in the originating Uri.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
jennyf19commented, Oct 6, 2020

Included in 1.1.0 release.

0reactions
robi26commented, Oct 5, 2020

We have the same problem with malformed redirect URLs when hosting the blazor (server-side) web app in a directory / virtual path: The wrong URL looks like this: https://localhost:44398//sampleMicrosoftIdentity/Account/Challenge?redirectUri=https://localhost:44398//sample&scope=user.read openid offline_access profile&loginHint=&domainHint=&claims=&policy=

Read more comments on GitHub >

github_iconTop Results From Across the Web

ASP.NET MVC: Redirecting errors in partial to main view
The exception in question doesn't seem to affect the return of the partial, only some of the data within it - so the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found