Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto windows login #6

Closed
AdamNaylor opened this issue May 23, 2014 · 25 comments
Closed

Auto windows login #6

AdamNaylor opened this issue May 23, 2014 · 25 comments
Labels

Comments

@AdamNaylor
Copy link

Hi Mohammad,

This is a very good example of mixed authentication. Thanks for this. With this example, how would you go about automatically attempting to logon via Windows authentication (i.e., not having a "Windows" login button on the view), so when the application is loaded, it automatically attempts to login via Windows authentication, and if it fails, to display the Forms login view?

@MohammadYounes
Copy link
Owner

You could do the following:

1. Startup.Auth.cs: Change LoginPath to point to Windows Login handler

LoginPath = new PathString("/Windows/Login")

2. AccountController.Windows.cs: Update WindowsLogin action to allow both GET/POST and remove ValidateAntiForgeryToken attribute:

[AllowAnonymous]
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public async Task<ActionResult> WindowsLogin(string userName, string returnUrl)
{ 
   .
   .
   .
}

3. Web.config:

a) Update Windows Login Handler registration to allow GET/POST

<add name="Windows Login Handler" path="Login" verb="GET,POST" type="MixedAuth.WindowsLoginHandler" preCondition="integratedMode" />

b) Add a custom error page configuration, as described here. But make the 401.html automatically redirects users to the normal Login action:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title></title>
  <meta http-equiv="refresh" content="0;URL='/MixedAuth/Account/Login'" />    
</head>
<body>
  Invalid credentials 
</body>
</html>

4. AccountController.cs : Make sure that Logoff action redirects to an Action that does not require authorization, or else users will be redirected to the Windows Login handler and automatically signed in again! giving the impression that Logoff action is not working.

@Aleksanderis
Copy link

This approach doesn't work for me. Should it work having "Anonymous Authentication" = Enabled?
In my scenario I need Anonymous authentication as well, but anyway - I tried disabling it, and it still doesn't work. With disabled anonymous auth - it goes in endless loop.
Seems there is something missing.. any ideas what? ;)

@MohammadYounes
Copy link
Owner

Are you sure you followed the steps exactly as described ? Try them on a fresh copy of the repository, and pay attention to step 4.

@MohammadYounes
Copy link
Owner

Also note, the repo. has Authorize attribute assigned to the home controller, and the LogOff action is set to redirect to "Home/Index", make the following update and test:

[Authorize]
public class HomeController : Controller
{
  [AllowAnonymous]
  public ActionResult Index()
  {
    return View();
  }
}

Does this help ? if not, what URLs are making the loop ?

@Aleksanderis
Copy link

Thanks for so quick response. It's being late here, so I won't argue too much about what I missed - it might be really so.:) However it looks like I did everything like you described. Regarding the 4 step - it's about logoff logic, but I'm not being logged in, so probably it's not the case.

Although I implemented it in a slightly different way - without 401.html page with Meta-refresh, which looked strange for me.
It seems it works now for me. What I did - I enabled GET support for WindowsLogin action like you advised, but further I went another way. I implemented AuthenticationFilter which is registered globally excluding Login page, so that user could open login page and automatic login wouldn't happen. This way user can login using different account type.
In this filter I'm just calling Windows\Login action, which works now with GET. In Logoff action I'm setting a "ForcedLogout" cookie, which prevents autologin if user was logged out consciously.

Not sure if AuthenticationFilter is neccesary here, might be something similar can be achieved just using one of global.asax life-cycle methods..
It's just a basic idea, I'm not sure it can be called a good practice, but comparing to how I was trying to implement it at the beginning - now it looks like pretty simple, and more important - it works. :)

@MohammadYounes
Copy link
Owner

The 401.html approach is least invasive, as it does not mess with the flow. It requires no additional configuration.

You excluded the login action from your filter, what about other anonymous actions! do you need to keep excluding them too ?

@Aleksanderis
Copy link

The 401.html approach didn't work for me, because it wasn't happening automatically by some reason. What you mean by "requires no additional configuration" is also not clear, because you pointed 4 steps of additional configuration yourself. :) I just configured/developed it in a slightly different way.

Regarding anonymous access - it's a bit more complicated here, because user always can logout and continue browsing anonymously. Automatic windows login is needed only for the first login. So to what my filter will be attached and to what it won't - it's not so critical, it's just a matter of the first access to a site.
Sorry, I guess it sounds confusing, but not sure how to explain it better. :) Perhaps it doesn't make sense for a real site scenario, but what I'm doing - it's just some experiments and practice with ASP.NET MVC, ASP.NET Identity, different authentication methods, etc.

@MohammadYounes
Copy link
Owner

I meant no additional configuration after initial setup 😄

Why not to make a fork and share your modifications, I'm not sure I do understand what you did!

@Aleksanderis
Copy link

What I did - I think it's a bit too specific for my app.
When I will have some spare time - I will try to isolate the behavior I needed in some smaller project, that would do more sense for others, and post it in a fork or something like that.
Anyway, once more - thanks for your help and feedback.

@MohammadYounes
Copy link
Owner

That would be great! Thanks.

@AdamNaylor
Copy link
Author

Hi Mohammad,

Thanks for coming back to me so quickly.

My intention is to determine whether the user is a member of particular AD groups. I believe the best way to do this would be by adding the following to the WindowsLogin method in AccountController.Windows.cs:

  bool membershipPass = false;
  foreach (System.Security.Principal.IdentityReference sid in Request.LogonUserIdentity.Groups)
  {
      System.Security.Principal.NTAccount ntAccount = (System.Security.Principal.NTAccount)sid.Translate(typeof(System.Security.Principal.NTAccount));
      if (ntAccount.ToString().Equals(@"ADomain\ADomainGroup"))
      {
          membershipPass = true;
      }
  }

    //If not a member of the specified group(s), then need to redirect down the forms login route
  if (!membershipPass)
  {
          .....
  }

Do you agree? How would you then redirect to the forms login route? Would the LoginPath option of the "UseCookieAuthentication" need to be changed?

@MohammadYounes
Copy link
Owner

Hi Adam,

Yes, I would add such logic there! and No, you don't need to change the LoginPath.

Following the same flow as when the windows user name is already used by another local login, the user is redirected to WindowsLoginConfirmation view, to be able to choose another one, then back to the windows login normal flow. You could do the same, informing users they are not allowed using the provided credentials.

One last thing, be aware that not all AD groups are available using LogonUserIdentity.Groups, see this issue #3. (especially this).

@AdamNaylor
Copy link
Author

Thanks for That Mohammad. So in the if (!membershipPass) condition, I could just call:

return View("Login", new LoginViewModel { });

@MohammadYounes
Copy link
Owner

Yes, it will act the same as when the user is not windows authenticated:

//
// POST: /Account/WindowsLogin
[AllowAnonymous]
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<ActionResult> WindowsLogin(string userName, string returnUrl)
{
  if (!Request.LogonUserIdentity.IsAuthenticated)
  {
    return RedirectToAction("Login");
  }
  .
  .
  .
}

@AdamNaylor
Copy link
Author

Of course. Thank you.

@AdamNaylor
Copy link
Author

Hi Mohammad. Sorry, another one for you.

Once logged in, I want to retrieve data from other tables. Would you add a secondary DB Context to interact with these new tables? The reason I'm asking this is because at the moment ApplicationDbContext is derived from IdentityDbContext, rather than the normally used DbContext class.

Does this make sense?

@MohammadYounes
Copy link
Owner

Have a look at this question and answer

@AdamNaylor
Copy link
Author

Thanks Mohammad. Got that sorted now.

Something needs to be updated to get the Logoff working correctly. Currently, in LoginPartial, "LogOff" is called. I'm guessing some kind of conditioning is required here to determine whether to call "LogOff" or "WindowsLogoff"?

If I change LoginPartial to call "WindowsLogOff", the AuthenticationManager.SignOut() method is called, and it goes to a blank page. I guess here, I'd need to redirect to a "You have successfully Logged out page"?

Also, When calling the SignOut() method, shouldn't the records be removed from the "Users" and "UserLogins" tables, or should they remain in there?

@MohammadYounes
Copy link
Owner

The WindowsLogOff action purpose is to force authentication when linking a local account with a windows account, see WindowsLoginHandler Line 34.

You should be calling the normal LogOff action, and redirect to your desired view. But note that the browser will keep the windows credentials until you close it, so may be its better to display a message informing the user, something like:"You logged out successfully. To complete the log out process, it is recommended that you close the browser window."

As for "Users" and "UsersLogins" tables, these hold the registered users along with their linked logins, you should keep them as long as you want to keep your users!

@AdamNaylor
Copy link
Author

Hi Mohmmad, I've done that, however, the ProcessRequestAsync is called, checks whether context.User.Identity.IsAuthenticated is false (which it is), and then logs on again. This is due to the change we made above to automatically login as Windows, by changing the LoginPath variable to "//Windows/Login". What would be cool, is if after "LogOff" is called, and AuthenticationManager.SignOut() is run, the original Forms or Windows login page is shown. I've tried this by calling return RedirectToAction("Login", "Account") after AuthenticationManager.SignOut(), and it seems to work. Does this look valid to you? Do you reckon anything else is required to completely logoff?

@MohammadYounes
Copy link
Owner

As I said before:

Make sure that Logoff action redirects to an Action that does not require authorization, or else users will be redirected to the Windows Login handler and automatically signed in again! giving the impression that Logoff action is not working.

@AdamNaylor
Copy link
Author

ok Thanks. So my change above should be sufficient then. Thanks again.

@AdamNaylor
Copy link
Author

Hi Mohammad. Thank you for all of you help in the past few weeks. Its been really helpful to me.
Recently, I have upgraded my version of VS2013 to Update 2. I have been working on a solution whereby auto email verification/confirmations are sent if registering with an external account. I have got this working well. This uses Identity v2.

I have tried to merge in the MixedAuth code into the project to allow internal users to login automatically. However, I have had difficulty in merging in the code. Thus far, the Windows\Login method isn't auto called for some reason. If manually type in the Windows\Login path, the ProcessRequestAsync is called, but isn't run through the second time for the secondary request, like in the MixedAuth project. Instead, after running through the ProcessRequestAsync code once, it fails, reporting "The custom error module does not recognize this error.".

Long shot, but would you have any idea what this means? I can't even see where it is being generated from.

@AdamNaylor
Copy link
Author

Additionally, since upgrading to Update 2, if I open the original MixedAuth project, the "DefaultConnection" database won't create when trying to register a new user. It states:
"Cannot attach the file 'C:\MVC5-MixedAuth-master\src\App_Data\MVC5-MixedAuth.mdf' as database 'MVC5-MixedAuth'. "

@MohammadYounes
Copy link
Owner

Moved to a new issue #11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants