3

I have an Asp.Net MVC webapplication sitting inside a website still largely managed by delphi. Security is currently managed by delphi which creates cookies.

It has been decided to authenticate users within the ASP.Net application by extracting the cookie details and passing them to an imported Delphi DLL which returns true or false depending on whether the user is valid.

My plan was to use Forms authentication, but instead of redirecting the user to a form instead call the delphi wrapper and if successful, redirect the user to the original url. This gives the benefit that when security is migrated to .Net, the authentication framework will already exist, just the implementation will need to change.

public ActionResult LogOn(SecurityCookies model, string returnUrl)
    {
        try
        {
            if (model != null)
            {
                Log.DebugFormat("User login: Id:{0}, Key:{1}", model.UserId, model.Key);
                if (authenticator.UserValid(model.UserId, model.Key, 0))
                {
                    FormsService.SignIn(model.UserId, false);
                    return Redirect(returnUrl);
                }
            }
...

Note that the SecurityCookies are generated by a custom binding class from the delphi generated cookie - this works well.

The call to the delphi dll also works ok.

The issue I have to overcome is that nearly all of the calls to the .Net application are ajax requests. However when the user is not logged in, the browser makes 3 calls due to the redirects: 1) Original ajax request 2) Redirect to ~/Account/Logon (code above) 3) Redirect back to original ajax request

Although tracking the responses posted back to the client, show that Step 3 returns the correct data, overall the process fails for an as yet undetermined reason. Simply clicking refresh on the client works because now the user is authenticated and the redirect to ~/account/Logon doesn't occur.

Note my client jQuery code is as follows: $.getJSON(requestString, function (data) { //do something with the data });

Is there a way of changing the Forms Authentication process so that instead of redirecting to a Url when the User is not authenticated, I can run some other code instead? I'd like the fact that authentication has taken place to be completely invisible to the User's browser.

6
  • could you rewrite your last statement? Commented May 19, 2011 at 14:09
  • have you considered wrapping the authentication call in a membership provider MSDN? Commented May 19, 2011 at 14:16
  • Sorry Shawn, is that clearer now? Commented May 19, 2011 at 14:22
  • @Menahem: I have, however I'm not clear WHERE I would authenticate in that case? Putting it within the Controller method would be poor as caching would bypass security checks, and I'm not keen on the idea of putting it in Global.asax. As I see it Custom Membership providers still work with a Logon.aspx form -it's just how you managed the credentials that changes. Commented May 19, 2011 at 14:36
  • if you are using a provider, you only need to define access to your pages in the web.config , and the user will get redirected / authenticated without putting any code in global.asax or the controller. Commented May 19, 2011 at 14:52

3 Answers 3

8

If you want to authenticate the request, the place to do this is in global.asax.cs by defining the Application_AuthenticateRequest method. Here you can read out the custom cookies with the imported delphi dll and set the Context.User. All authorization in asp.net is based on the user that is set in the HttpContext. An example of an implementation of the Application_AuthenticateRequest method:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
    if(authCookie != null)
    {
        //Extract the forms authentication cookie
        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        // Create an Identity object
        //CustomIdentity implements System.Web.Security.IIdentity
        CustomIdentity id = GetUserIdentity(authTicket.Name);
        //CustomPrincipal implements System.Web.Security.IPrincipal
        CustomPrincipal newUser = new CustomPrincipal();
        Context.User = newUser;
    }
}

If the cookie is not valid then you won't set a user in the context.

You can then create a BaseController which all your controllers will inherit from that checks if the user that is provided in the context is authenticated. If the user is not authenticated you can return a HttpUnauthorizedResult.

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (User == null || (User != null && !User.Identity.IsAuthenticated))
        {
           filterContext.Result = new HttpUnauthorizedResult();
        }
        else
        {
            // Call the base
            base.OnActionExecuting(filterContext);
        }
    }
}

And in your web.config:

<authentication mode="None"/>

because you don't want the request to be redirected to a login page.

Sign up to request clarification or add additional context in comments.

Comments

1

I'm going to answer a part of this specific question

Is there a way of changing the Forms Authentication process so that instead of redirecting to a Url when the User is not authenticated, I can run some other code instead? I'd like the fact that authentication has taken place to be completely invisible to the User's browser.

No there is not. I wrote a login system that is similar to yours being that it is built ontop of forms authentication and the fact there is no extensibility point made it a complete PITA to write.

The way I was able to override the built in behavior was using a HttpModule that monitors for redirects to the forms auth URL and then to intercept that action and lets me handle it.

internal static bool IsFormsAuthenticationLogin(HttpResponseBase response)
{
    if (!response.IsRequestBeingRedirected) return false;
    if (string.IsNullOrWhiteSpace(response.RedirectLocation)) return false;

    Uri formsAuthUri;
    try
    {
        formsAuthUri = new Uri(FormsAuthentication.LoginUrl);
    }
    catch (Exception ex)
    {
        throw new XifCriticalException("Could not construct formsAuthUri", ex);
    }


    Uri redirctUri;
    try
    {
        redirctUri = new Uri(response.RedirectLocation, UriKind.RelativeOrAbsolute);
    }
    catch (Exception ex)
    {
        throw new XifCriticalException("Could not construct redirctUri", ex);
    }

    try
    {
        //Check if the request was redirected by Forms Auth
        bool isFormsAuthenticationLogin = redirctUri.IsAbsoluteUri &&
                                            redirctUri.Host == formsAuthUri.Host
                                            && redirctUri.PathAndQuery.Contains(formsAuthUri.PathAndQuery); 

        return isFormsAuthenticationLogin;
    }
    catch (Exception ex)
    {
        throw new XifCriticalException("Could not construct isFormsAuthenticationLogin", ex);
    }
}

One other note, to be able to rely on this code in MVC3 you may also need to specify the app settings

 <add key="enableSimpleMembership" value="false" />
 <add key="autoFormsAuthentication" value="false" />

Comments

0

Couple things.

First, since you're apparently in a controller action, you should replace

Server.Transfer(returnUrl, true);

with

return Redirect(returnUrl);

Second, I generally prefer to not use ajax calls to do authentication. Users should explicitly move from unauthenticated to authenticated states.

Third, if you must do ajax calls where you would normally do redirects, you can do one of two things. In your controller, you can determine it's an ajax request by calling the extension method IsAjaxRequest() (returns true if it is) and branching a different result if it is an ajax request. Second, if you return a Redirect, then it will return to the client a HTTP redirect, which the Ajax client code should be able to read and respond to (e.g. by reading the Location and then doing an ajax fetch on that). Again, I don't recommend this course, but it's possible.

Finally, as a complete left turn... have you considered leaving forms auth alone and instead using a custom MembershipProvider? You can use that to validate membership via Delphi and then set the client cookies using the normal FormsAuth objects like they do in the sample AccountController in MVC.

5 Comments

OK yes - I was originally using Redirect - referred to in my comments, but the code I posted was from when I tried Server.Transfer to see if it helped - then I realised I still had the Redirect to the Account/Logon method to deal with anyway
I'm sorry don't understand how your 2nd comment applies to what I'm doing here. The user is already authenticated on the web server, just not within the .Net code. The .Net application is currently only serving a small number of ajax requests, but needs to be secure.
They're not authenticated in the application yet (the ".net code"). But to each their own, that was advice not answer.
Sorry Paul, I didn't mean to offend - the advice is welcome. It's just that I'm rather constrained by the legacy app in terms of what I can do here.
Understood, and no offense taken. The "Third" paragraph should be relevant for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.