Forms authentication: disable redirect to the login page

后端 未结 12 2518
情歌与酒
情歌与酒 2020-12-02 09:29

I have an application that uses ASP.NET Forms Authentication. For the most part, it\'s working great, but I\'m trying to add support for a simple API via an .ashx file. I wa

相关标签:
12条回答
  • 2020-12-02 10:15

    I know there is already an answer with tick but while trying to solve similar problem I found this (http://blog.inedo.com/2010/10/12/http-418-im-a-teapot-finally-a-%e2%80%9clegitimate%e2%80%9d-use/) as an alternative.

    Basically you return your own HTTP status code (e.g. 418) in your code. In my case a WCF data service.

    throw new DataServiceException(418, "401 Unauthorized");
    

    Then use a HTTP module to handle it at the EndRequest event to rewrite the code back to 401.

    HttpApplication app = (HttpApplication)sender;
    if (app.Context.Response.StatusCode == 418)
    {
        app.Context.Response.StatusCode = 401;
    }
    

    The browser / client will receive the correct content and status code, it works great for me :)

    If you are interested to learn more about HTTP status code 418 see this question & answer.

    0 讨论(0)
  • 2020-12-02 10:24

    ASP.NET 4.5 added the Boolean HttpResponse.SuppressFormsAuthenticationRedirect property.

    public void ProcessRequest(HttpContext context)
    {
        Response.StatusCode = 401;
        Response.StatusDescription = "Authentication required";
        Response.SuppressFormsAuthenticationRedirect = true;
    }
    
    0 讨论(0)
  • 2020-12-02 10:25

    That's a known issue, and there's a NuGet Package for that and/or the source code available.

    0 讨论(0)
  • 2020-12-02 10:25

    Funny hack if you use.NET Framework >= v4.0 but < v4.5. It uses reflection to set value of inaccessible SuppressFormsAuthenticationRedirect property:

    // Set property to "true" using reflection
    Response
      .GetType()
      .GetProperty("SuppressFormsAuthenticationRedirect")
      .SetValue(Response, true, null);
    
    0 讨论(0)
  • 2020-12-02 10:25

    I had the problem that I wanted to avoid not only the redirect but also the forms authentication itself in order to make a web api work. Entries in web.config with a location tag for the api didn't help. Thus I used SuppressFormAuthenticationRedirect and HttpContext.Current.SkipAuthorization to suppress the authentication in general. In order to identify the sender I used e.g. the UserAgent in the Header but it is of course recommendable to implement further authentification steps, e.g. check against the sending IP or send another key with the request. Below is inserted in the Global.asax.cs.

    protected void Application_BeginRequest(object sender, EventArgs e)
        {
            if (HttpContext.Current.Request.UserAgent == "SECRET-AGENT")
            {
                AppLog.Log("Redirect suppressed");
    
                HttpApplication context = (HttpApplication)sender;
    
                context.Response.SuppressFormsAuthenticationRedirect = true;
                HttpContext.Current.SkipAuthorization = true;                
            }
        }
    
    0 讨论(0)
  • 2020-12-02 10:31

    After a bit of investigation, it looks like the FormsAuthenticationModule adds a handler for the HttpApplicationContext.EndRequest event. In it's handler, it checks for a 401 status code and basically does a Response.Redirect(loginUrl) instead. As far as I can tell, there's no way to override this behaviour if want to use FormsAuthenticationModule.

    The way I ended up getting around it was by disabling the FormsAuthenticationModule in the web.config like so:

    <authentication mode="None" />
    

    And then implementing the Application_AuthenticateEvent myself:

    void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        if (Context.User == null)
        {
            var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName);
            if (oldTicket != null && !oldTicket.Expired)
            {
                var ticket = oldTicket;
                if (FormsAuthentication.SlidingExpiration)
                {
                    ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);
                    if (ticket == null)
                        return;
                }
    
                Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);
                if (ticket != oldTicket)
                {
                    // update the cookie since we've refreshed the ticket
                    string cookieValue = FormsAuthentication.Encrypt(ticket);
                    var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ??
                                 new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };
    
                    if (ticket.IsPersistent)
                        cookie.Expires = ticket.Expiration;
                    cookie.Value = cookieValue;
                    cookie.Secure = FormsAuthentication.RequireSSL;
                    cookie.HttpOnly = true;
                    if (FormsAuthentication.CookieDomain != null)
                        cookie.Domain = FormsAuthentication.CookieDomain;
                    Context.Response.Cookies.Remove(cookie.Name);
                    Context.Response.Cookies.Add(cookie);
                }
            }
        }
    }
    
    private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
    {
        FormsAuthenticationTicket ticket = null;
        string encryptedTicket = null;
    
        var cookie = context.Request.Cookies[name];
        if (cookie != null)
        {
            encryptedTicket = cookie.Value;
        }
    
        if (!string.IsNullOrEmpty(encryptedTicket))
        {
            try
            {
                ticket = FormsAuthentication.Decrypt(encryptedTicket);
            }
            catch
            {
                context.Request.Cookies.Remove(name);
            }
    
            if (ticket != null && !ticket.Expired)
            {
                return ticket;
            }
    
            // if the ticket is expired then remove it
            context.Request.Cookies.Remove(name);
            return null;
        }
    }
    

    It's actually slightly more complicated than that, but I basically got the code by looking at the implementation of FormsAuthenticationModule in reflector. My implementation is different to the built-in FormsAuthenticationModule in that it doesn't do anything if you respond with a 401 - no redirecting to the login page at all. I guess if that ever becomes a requirement, I can put an item in the context to disable the auto-redirect or something.

    0 讨论(0)
提交回复
热议问题