0

I have made razor page to log out users. When authenticated user visits route /account/logout I want to show him the page with success message, if user is anonymous then page is unathorized. However this success page must not be accessible by direct url input.

Following code works well except anyone can navigate to /account/logout/success which acts as normal page (and since it's not one responsible for signing out, it's potentially confusing).

public class LogoutModel : CustomPageModel
{
    private readonly SignInManager _signInManager;

    public LogoutModel(SignInManager signInManager) => _signInManager = signInManager;

    public async Task<IActionResult> OnGetAsync()
    {
        if (_signInManager.IsSignedIn(User))
        {
            await _signInManager.SignOutAsync();
            return RedirectToPage("Logout", "Success");
        }

        return Unauthorized();
    }

    public void OnGetSuccess()
    {
    }
}

How do I prevent handler OnGetSuccess to be accessible directly?

0

3 Answers 3

1

I've solved it by using Attribute and IPageFilter which checks the Referer header.

First I've created the Attribute

[AttributeUsage(AttributeTargets.Method)]
public class ChildHandlerAttribute : Attribute
{
}

Then I've decorated the GetOnSuccess() handler with it

[ChildHandler]
public void OnGetSuccess()
{
}

Then I've implemented the filter that checks Referer header for child handlers.

public class ChildHandlerAsyncPageFilter : IAsyncPageFilter
{
    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
    {
        var pageHandlerExecutedContext = await next();

        if (pageHandlerExecutedContext.HandlerMethod?.MethodInfo.GetCustomAttribute<ChildHandlerAttribute>() == null)
        {
            return;
        }

        var referrer = context.HttpContext.Request.Headers["Referer"].ToString();
        var request = pageHandlerExecutedContext.HttpContext.Request;

        if (!referrer.StartsWith($"{request.Scheme}://{request.Host}"))
        {
            pageHandlerExecutedContext.Result = new NotFoundResult();
        }
    }

    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) => Task.CompletedTask;
}

Lastly I've added the filter into the pipeline in Startup.cs

services.AddMvc(options =>
{
    options.Filters.Add<ChildHandlerAsyncPageFilter>();
});
Sign up to request clarification or add additional context in comments.

Comments

0

Se this question for how it might be possible.

Not sure if I recommend going that route since its a bit dirty and a uneccesarily complex solution. You could return a different status messages for logged in/out users.

Logout.cshtml.cs

public class LogoutModel : CustomPageModel
{
    private readonly SignInManager _signInManager;

    public LogoutModel(SignInManager signInManager) => _signInManager = signInManager;

    public string StatusMessage { get; set; }

    public async Task<IActionResult> OnGetAsync()
    {
        if (_signInManager.IsSignedIn(User))
        {
            await _signInManager.SignOutAsync();
            StatusMessage = "Successfully logged out!";
            return Page();
        }
        else {
            StatusMessage = "Already logged out!";
            return Page();
        }

    }
}

Logout.cshtml

@page
@model X.LogoutModel

@Model.StatusMessage

4 Comments

Redirecting to pages for contextual status is my problem, they are always accessible directly. Say if user logs in and goes to Page1 (because he might have that saved in history), page will say it successfully logged him out, but it didn't, because that didn't go through the Logout handler which calls the SignOutAsync().
Thats why I suggested to [Authorize] to Page1 class, requiring login to access it. Or you could redirect the user if he accesses it logged in.
You can't do that, by the time you redirect to Page1, he's no longer authenticated. At first I used return Page() to show contents of Logout page which holds the success message. It would solve the issue at hand, but would introduce another issue where user is presented with content as if he was still authenticated (while he's already not), until he refreshes the page.
Ah ofc, my bad, so that alternative was a bad suggestion. I have edited my answer and adjusted the code to only reflect the status message suggestion.
0

In your scenario, the user is outs and redirects as an anonymous user. So it can not be said that here only certain users have access to this action.

You should solve this in other ways. for example, you can set value in cookie at LogOut Action and then check in Success Action. For get more security apply the pattern that MVC uses for the CSRF.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.