Lock users out of Active Directory with ASP.NET application - asp.net

We have an intranet ASP.NET 4.0 application and use forms authentication where employees authenticate against Active Directory to log in.
We need to lock users out of AD after too many failed password attempts (number is set in domain policy).
As it works now, users get locked out of the application only but not out of AD. We need to lock them in AD and they will need to call help desk to unlock them.
I saw this http://msdn.microsoft.com/en-us/library/ms998360.aspx, where it is stated under "Account Lockout" that ActiveDirectoryMembershipProvider locks users out of provider but not out of AD.
But how do we lock users in AD then?
web.config:
<membership defaultProvider="MyADMembershipProvider">
<providers>
<add name="MyADMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="administrator"
connectionPassword="passw0rd"
attributeMapUsername="sAMAccountName" />
</providers>
</membership>
Login.aspx:
<asp:Login ID="Login1" runat="server" DisplayRememberMe="False" FailureText="Wrong user name or password." DestinationPageUrl="~/User.aspx" OnLoggedIn="Login1_LoggedIn" OnLoginError="Login1_LoginError">
Login.aspx.cs
protected void Login1_LoginError(object sender, EventArgs e)
{
string userName = Login1.UserName;
if (!string.IsNullOrEmpty(userName))
{
// Get information about this user
MembershipUser usr = Membership.GetUser(userName);
if (usr != null)
{
// check to see if the error occurred because they are not approved
if (!usr.IsApproved)
{
Login1.FailureText = "Your account has not yet been approved by an administrator.";
}
// check to see if user is currently locked out
else if (usr.IsLockedOut)
{
Login1.FailureText = "You have been locked out of your account due to too many failed passwords. Call help desk to unlock it.";
}
}
}
}

Related

WebForms FormsAuthentication - Redirect after authentication results in Login Page to be called again

I have 2 ASP.NET WebForms projects under 2 different domain names. The first is merely a file server where important documents are stored. I am busy implementing FormsAuthentication so that if someone enters the URL of a document in a web browser, a redirect is done to a login page of the second WebForms project under a seperate domain name. I have pasted the Web.config content of the first File Server project and then the code from the second project to Authorize the download of the document. You will see that the code is not yet complete. What happens that the redirect is done to the login page. When authenticated, the redirect is done to the file, but the file server or first app sees this as a new attempt of accessing the file and redirects again to the login page. It is as if the cookie is not received from the file server.
Help will so much be appreciated.
App 1 - Just to store files.
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
<remove name="DefaultAuthentication" />
<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="" />
</modules>
</system.webServer>
App 2 - Doing authentication and directs to the file on the fileserver (Note that both apps are on different domains)
protected void btnLogin_Click(object sender, EventArgs e)
{
bool Validated = true; // Still to be implemented
if (Validated)
{
FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket(1, ".ASPXSAFECYTE", DateTime.Now, DateTime.Now.AddMinutes(30), true, "");
string CookieString = FormsAuthentication.Encrypt(Ticket);
HttpCookie Cookie = new HttpCookie(FormsAuthentication.FormsCookieName, CookieString);
Cookie.Expires = Ticket.Expiration;
Cookie.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(Cookie);
if (Request["ReturnUrl"] == null)
Response.Redirect("~/Login.aspx", true);
else
{
string ReturnUrl = SystemProperties.FilesServerAddress.TrimEnd(new char[] { '/' }) + Request["ReturnUrl"];
Response.Redirect(ReturnUrl, true);
}
}
}
Kind regards,
Jaco
For security purposes, Cookies can only be created for the hosting domain and/or it's subdomains. Therefore, creating a Cookie from one domain for another will not work.

How does WIF (WSFederationAuthentication) know which user I am?

I've been put in charge of figuring out a way to allow users to authenticate into our forms based site. Our corporate IT has set up a development adfs server, and the relying party trust has been set up to one of our dev environments.
I've been reading and looking for tutorials for about two weeks, so I'm by no means an expert, and I can't seem to understand how the STS (our ADFS server) is supposed to figure out which user is requesting authentication.
I've been following Wiktor Zychla's blog because it actually includes code examples and messes less with the web.config as opposed to most other tutorials. (http://www.wiktorzychla.com/2014/11/simplest-saml11-federated-authentication.html)
Here's the pipeline so far as I can see it.
User comes to the log in page.
Page_Load automatically redirects user to ADFS
User gets authenticated and redirected back to the log in page
In the Page_Load I then consume some token and use that to authorize
the user.
Redirect the user to a page that requires authentication.
Code and Web.config changes I've made to the site:
protected void Page_Load(object sender, System.EventArgs e)
{
var sam = FederatedAuthentication.SessionAuthenticationModule;
var fam = new WSFederationAuthenticationModule();
fam.FederationConfiguration = FederatedAuthentication.FederationConfiguration;
var request = new HttpContextWrapper(this.Context).Request;
if (UseADFS)
{
// is this the response from the STS
if (!fam.IsSignInResponse(request))
{
// no
// the STS
fam.Issuer = WsFederationIssuerName;
// the return address
fam.Realm = WsRealm;
fam.Reply = WsReply;
var req = fam.CreateSignInRequest(string.Empty, null, false);
// go to STS
Response.Redirect(req.WriteQueryString());
}
// is this the response from the STS
else
{
// yes, get the SAML token
var securityToken = fam.GetSecurityToken(request);
var config = new SecurityTokenHandlerConfiguration
{
CertificateValidator = X509CertificateValidator.None,
IssuerNameRegistry = new CustomIssuerNameRegistry()
};
config.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
var tokenHandler = new SamlSecurityTokenHandler
{
CertificateValidator = X509CertificateValidator.None,
Configuration = config
};
// validate the token and get the ClaimsIdentity out of it
var identity = tokenHandler.ValidateToken(securityToken);
var principal = new ClaimsPrincipal(identity);
var token = sam.CreateSessionSecurityToken(principal, string.Empty,
DateTime.Now.ToUniversalTime(), DateTime.Now.AddMinutes(20).ToUniversalTime(), false);
sam.WriteSessionTokenToCookie(token);
if (identity[0].IsAuthenticated)
{
//string email = principal.Claims.Where(x => x.Type == ClaimTypes.Email).Select(x => x.Value).SingleOrDefault();
string name = principal.Claims.Where(x => x.Type == ClaimTypes.Name).Select(x => x.Value).SingleOrDefault();
CustomClaimsObject claim = new CustomClaimsObject(name, principal);
doSigninWork(true, claim);
}
}
}
else if (!this.IsPostBack && !fam.IsSignInResponse(request))
{
Session.Abandon();
}
}
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
</configSections>
<httpModules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler"/>
</httpModules>
<modules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler"/>
</modules>
<system.identityModel>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
</federationConfiguration>
</system.identityModel.services>
<authentication mode="Forms">
<forms loginUrl="Public/invalidlogin.aspx?err=sessiontimeout" protection="All" requireSSL="false" slidingExpiration="true"/>
</authentication>
How is the STS supposed to figure out who is requesting authentication? I'm not posting any relevant user info to it. Am I supposed to add something to my request? Or is the context somehow supposed to have relevant data? Or does the ADFS box need to have a log in where the user enters their credentials?
Right now when I navigate to the user to (by way of the button click) https://xxx-xxx.xxxxx.xxxxxx.com/adfs/ls/ I get this error.
Encountered error during federation passive request.
Additional Data
Protocol Name:
wsfed
Relying Party:
Exception details:
Microsoft.IdentityServer.Web.CookieManagers.InvalidContextException: MSIS7001: The passive protocol context was not found or not valid. If the context was stored in cookies, the cookies that were presented by the client were not valid. Ensure that the client browser is configured to accept cookies from this website and retry this request.
at Microsoft.IdentityServer.Web.Protocols.GenericProtocolRequest.ParseEncodedRequestParts(String[] encodedRequestParts)
at Microsoft.IdentityServer.Web.Protocols.GenericProtocolRequest..ctor(String encodedGenericRequest)
at Microsoft.IdentityServer.Web.Protocols.WSFederation.WSFederationProtocolHandler.GetOriginalRequestFromResponse(ProtocolContext context)
at Microsoft.IdentityServer.Web.PassiveProtocolListener.ProcessProtocolRequest(ProtocolContext protocolContext, PassiveProtocolHandler protocolHandler)
at Microsoft.IdentityServer.Web.PassiveProtocolListener.OnGetContext(WrappedHttpListenerContext context)
Can someone kick me in the right direction? I'm sure I'm missing something obvious to anyone with experience.
Update:
Login with adfs working. But now I'm struggling a bit with the logout.
My logout code is below. Essentially whats happening is the authentication on my claims identity is still true even after I've attempted to "logout" with the code below. The only claim I'm sending from ADFS to my relying party is en E-mail claim. Not sure if that makes a difference.
WSFederationAuthenticationModule authModule = FederatedAuthentication.WSFederationAuthenticationModule;
string signoutUrl = (WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(WsFederationIssuerName, eURL.BuildURL(), null));
WSFederationAuthenticationModule.FederatedSignOut(new Uri(signoutUrl), null);
Update 2:
So I've realized the sign out is working correctly. However it seems ADFS is redirecting to my sign in page before it redirects to my log out page. This is causing the Sign in code to be run again which fires another claim to ADFS. Is there a way to stop this? Or a way to know if I'm coming from the adfs logout?
Update 3:
Solved the issue by wrapping an if statement around the if(UseADFS) clause to check to see where I'm coming from.
if(request.UrlReferrer.Authority.Contains(authority))
I just check to see if the place I'm coming from is my server (your adfs domain) vs (your website domain). This together with a parameter placed in my redirect url was enough to decipher whether the user was logging out or logging in.

Need help getting [Authorize(Roles="Admin")] and User.isInRole("Admin") working in Asp.Net identity 1

I just don't get how these two potentially incredibly useful functions are supposed to work.
[Authorize(Roles="Admin")] //Method A
and
User.isInRole("Admin") //Method B
They are clearly not working right now for me. I did a few hours of research:
I read that you need to implement System.Web.Security.RoleProvider, then set it up in the web config:
<roleManager defaultProvider="OdbcRoleProvider"
enabled="true"
cacheRolesInCookie="true"
cookieName=".ASPROLES"
cookieTimeout="30"
cookiePath="/"
cookieRequireSSL="false"
cookieSlidingExpiration="true"
cookieProtection="All" >
<providers>
<clear />
<add
name="OdbcRoleProvider"
type="Samples.AspNet.Roles.OdbcRoleProvider"
connectionStringName="OdbcServices"
applicationName="SampleApplication"
writeExceptionsToEventLog="false" />
</providers>
</roleManager>
This caused the RoleProvider I implemented to by constructed, but the role checking functions were certainly not calling any of the methods in there.
I then read that Asp.NET Identity does away with the RoleProvider, now you need to do this:
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthenticationModule" />
<remove name="RoleManager" />
</modules>
And I did that.
I have a custom UserManager that connects to my postgres backend. The problem is that whenever I use it, I need to instantiate one. It seems to me that if Functions A and B are going to work, then the UserManager I have implemented needs to be referenced in some sort of config file so Asp.NET knows about it implicitly. This would be just like the RoleManager in the past.
How does ASP.NET identity alter how Functions A and B check the roles from the old RoleProvider using behavior?
I figured it out.
When you call the login code like this:
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null && user.PasswordRequiresReset == false)
{
await SignInAsync(user, model.RememberMe); //triggers IUserRoleStore.GetRolesAsync
return RedirectToLocal(returnUrl);
}
SignInAsync triggers GetRolesAsync from the IUserRoleStore:
public Task<IList<string>> GetRolesAsync(TUser user) //where TUser is an ApplicationUser
{
Func<object, IList<string>> getRoles = (object user2) =>
{
TUser user3 = (TUser)user2;
return user3.Roles.Select(x => x.Role.Name).ToList();
};
return Task.Factory.StartNew(getRoles, user);
}
In the above, I chose to simply generate the roles from the roles I already loaded from the db and stored in the ApplicationUser object when I created it in FindAsync.
The roles from GetRolesAsync must be loaded in the cookie somewhere where they can be accessed quickly and easily by Functions A and B.

Getting the login user GUID in MVC 5

I am working on an ASP.NET MVC 5 web application, and I am using forms authentication against our LDAP server.
Here is the login action method:-
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
MembershipProvider domainProvider;
domainProvider = Membership.Providers["ADMembershipProvider"];
if (ModelState.IsValid)
And the related entities inside our web.config file, which connects to the LDAP:
<providers>
<add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="TestDomain1ConnectionString" connectionUsername="ad.user" connectionPassword="***" attributeMapUsername="sAMAccountName"/>
</providers>
<connectionStrings>
<add name="TestDomain1ConnectionString" connectionString="LDAP://ad-Tgroup.intra/OU=T,DC=ad-Tgroup,DC=intra"/>
</connectionStrings>
Currently I am storing the domainname\username inside our log table, as follows:
string ADusername = User.Identity.Name.ToString();
repository.InsertOrUpdateResturant(resturant, ADusername);
Using the User.Identity.Name might work in almost 95% of the cases because it can uniquely identify any user, but on the other hand, the username might be changed (let's say a user got married or divorced). So I am planning on replacing my User.Identity.Name and getting the user GUID instead. But I'm not sure if MVC 5 provides a way to get the login user GUID. For example, I cannot write User.Identity.GUID.

Single sign on inside asp.net mvc web application

I have two domains ,on our internal network:-
DomainA
DomainB
Both domains can communicate with each other’s, but they do NOT trust each other.
So currently I have deployed my asp.net MVC web application inside domainA on IIS, but I need users who are on DomainB Active directory to be able to login to the asp.net mvc using their domainB AD credentials . I am open to both windows authentication and form authentication inside my asp.net mvc .
But the only requirement that came from the client is that they want users who access the asp.net mvc intranet application from their machine on domainB, to be able to lo-gin to the system without having a login page; they can either:-
enter the username and password through the browser pop-up,
or to sign in automatically.
So can anyone advice what are the approaches I can follow, to achieve this?
Thanks
EDIT
I have read the following article http://msdn.microsoft.com/en-us/library/ff650307.aspx, about how i can authenticate asp.net mvc users from multiple domains, so inside my asp.net mvc i did the following :-
I added the following to my web.config:-
<system.web>
<membership>
<providers>
<add name="TestDomain1ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="TestDomain1ConnectionString" connectionUsername="ad-domainA.intra\it360ad.user" connectionPassword="$$$$$" />
</providers>
</membership>
&
<add name="TestDomain1ConnectionString" connectionString="LDAP://ad-domainA.intra/CN=Users,DC=ad-domainA,DC=intra" />
and i added the following Account.controller:-
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
MembershipProvider domainProvider;
domainProvider = Membership.Providers["TestDomain1ADMembershipProvider"];
// Validate the user with the membership system.
if (domainProvider.ValidateUser(model.UserName, model.Password))
{
// If there is a RequestUrl query string attribute, the user has
// been redirected to the login page by forms authentication after
// requesting another page while not authenticated.
if (Request.QueryString["ReturnUrl"] != null)
{
// RedirectFromLoginPage sets the authorization cookie and then
// redirects to the page the user originally requested.
// Set second parameter to false so cookie is not persistent
// across sessions.
FormsAuthentication.RedirectFromLoginPage(
model.UserName, false);
}
else
{
// If there is no RequestUrl query string attribute, just set
// the authentication cookie. Provide navigation on the login page
// to pages that require authentication, or user can use browser
// to navigate to protected pages.
// Set second parameter to false so cookie is not persistent
// across sessions.
FormsAuthentication.SetAuthCookie(model.UserName, false);
}
}
else
{
// Response.Write("Invalid UserID and Password");
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
////////////
//if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
//{
return RedirectToLocal(returnUrl);
//}
// If we got this far, something failed, redisplay form
}
But currently when the user try to login , he will get always the following message
•The user name or password provided is incorrect.
so can you advice if my code is correct ?
You have to deploy a SSO solution like Active Directory Federation Services on a server that is joined to DomainB.
Then implement authentication in your application (for example, WS-Federation Passive Requestor) that targets that SSO solution and standard Windows Authentication that targets DomainA.

Resources