Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Microsoft Identity I want to specify a Return Url when I call Sign In from my Blazor App

I have a C# Blazor Application using Microsoft Identity for authenticating users against Microsoft Entra.

The application is associated (via Client Id and secret) to a Microsoft Enrta Registered App with Redirect URI of https://mywebapp/signin-oidc.

In my application I register the CallbackPath in my Program.cs like thus:

builder.Services.AddAuthentication((o) =>
{
    o.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddMicrosoftIdentityWebApp((o) => {
        o.Instance = instance;
        o.ClientId = clientId;
        o.ClientSecret = clientSecret;
        o.TenantId = tenantId;
        o.Domain = domain;
        o.CallbackPath = "/signin-oidc";

Normally the flow is as follows:

  1. User goes to URL https://mywebapp
  2. If user is not logged in he is presented with a 'Sign In' button which when clicked he is redirected via:

Navigation.NavigateTo("MicrosoftIdentity/Account/SignIn");

  1. The Microsoft identity login dialog is displayed and the user logs in
  2. After login the user is presented back to https://mywebapp and he is now authenticated.

This is fine, but I also want to require sign in from other pages in the Web App.

What I want to do is to send the user a link to a specific page in my application ie. https://mywebapp/pagethree

This web page requires the user to be authenticated, so if he is not I want to direct him to the Microsoft login.

However after, he is logged in, I want him to be directed back to https://mywebapp/pagethree where he came from, but I don't know how to do it.

I've tried all kinds of permutations with MicrosoftIdentity/Account/SignIn?returnUrl or ?redirectUrl but whatever I put is ignored and the redirect is always to /signin-oidc and thence to the home page.

Any ideas for what should be a fairly basic functionality in a website.

Thanks.

Tim.

like image 389
TimBunting Avatar asked Nov 01 '25 05:11

TimBunting


2 Answers

The standard way to return to the same page after login is to use the NavigateToLogin extension method provided by NavigationManagerExtensions

  1. Add the Microsoft.AspNetCore.Components.WebAssembly.Authentication nuget package

  2. change the click action of your login button (even if it is a simple link) to call NavigateToLogin

     @inject Microsoft.AspNetCore.Components.NavigationManager Navigation
    
     <AuthorizeView>
         <Authorized>
             Hello, @context.User.Identity?.Name!
             <button class="nav-link btn btn-link" @onclick="BeginLogOut">Log out</button>
         </Authorized>
         <NotAuthorized>
             <a href="authentication/login" @onclick="BeginLogIn">Log in</a>
         </NotAuthorized>
     </AuthorizeView>
    
     @code{
    
         public void BeginLogIn()
         {
             Navigation.NavigateToLogin("MicrosoftIdentity/Account/SignIn");
         }
    
         public void BeginLogOut()
         {
             Navigation.NavigateToLogout("MicrosoftIdentity/Account/SignOut");
         }
     }
    

In the default template, a simple hyperlink is used, you can see in the above code example that @onclick added to an existing link will prevent the default href navigation and will execute the referenced code instead.

It is a lot simpler than that if you want to enforce login on certain pages and not have an unauthenticated view at all. In this case, in the razor page just add @attribute [Authorize] then if the user is unauthenticated when they reach that page or component, the user will be redirected to auth and will return to the same page.

If you want to return to a different page after the login, then you can use the NavigationManager directly instead of via the extension method. In this example there are 2 login buttons, with different return URLs configured:

@inject Microsoft.AspNetCore.Components.NavigationManager Navigation

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity?.Name!
        <button class="nav-link btn btn-link" @onclick="BeginLogOut">Log out</button>
    </Authorized>
    <NotAuthorized>
        <button class="nav-link btn btn-link" @onclick='() => BeginLogIn("/fetchdata")'>Log In - Fetch Data</button>
        <button class="nav-link btn btn-link" @onclick='() => BeginLogIn("/pagethree")'>Log In - Page Three</button>
    </NotAuthorized>
</AuthorizeView>

@code{

    public void BeginLogIn(string returnUrl)
    {
        Navigation.NavigateToLogin(
            "MicrosoftIdentity/Account/SignIn",
            new InteractionRequestOptions
            {
                Interaction = InteractionType.SignIn,
                ReturnUrl = returnUrl
            });
    }

    public void BeginLogOut()
    {
        Navigation.NavigateToLogout("MicrosoftIdentity/Account/SignOut");
    }
}
like image 63
Chris Schaller Avatar answered Nov 03 '25 22:11

Chris Schaller


I was struggling with issues from upgrading in my Blazor WASM app .Net6 to .Net8 where my app would just 'forget' the returnurl when logging in.

I found the following breaking change: https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/7.0/wasm-app-authentication

We updated the support for authentication in Blazor WebAssembly apps to rely on the history state instead of query strings in the URL. As a result, existing applications that pass the return URL through the query string will fail to redirect back to the original page after a successful login.

Existing applications should use the new NavigateToLogin extension method as it's able to flow the data to the login page correctly.

Version introduced

.NET 7

Previous behavior

The return URL was specified in the query string as ?returnUrl=<>

New behavior

Starting in .NET 7, the return URL and other parameters passed to the authentication/login page are passed via the history.state entry of the page.

Before, I was using the default RedirectToLogin page supplied with the Blazor WASM template as such:

@inject NavigationManager Navigation

@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
    }
}

The new version according to the above is:

@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> Options
@code {

    protected override void OnInitialized()
    {
        Navigation.NavigateToLogin(Options.Get(Microsoft.Extensions.Options.Options.DefaultName).AuthenticationPaths.LogInPath);
    }
}

Simply replacing the code worked for me. My return URL is now preserved.

like image 38
Xebozone Avatar answered Nov 03 '25 22:11

Xebozone



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!