Integration between our Asp.NET MVC form authentication and Office 365 - asp.net

I have the following:-
1) Intranet Asp.net MVC-4 web application, which currently authenticate the users using form authentication with our on-premises active directory through ldap string.
2) So users enter their username and password >> the application will authenticate their user names and passwords from AD and login them accordingly.
3) Now our organization will turn off the on-pressies AD, and we will migrate to office 365.
so i am not sure if i can modify my asp.net mvc-4 intranet (access from our internal network only) to authenticate the entered username and password from office 365 instead of using ldap string?
currently the login is been performed as follow:-
the login post action method:-
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Login(LoginModel model, string returnUrl)
{
MembershipProvider domainProvider;
domainProvider = Membership.Providers["Domain1ADMembershipProvider"];
if (ModelState.IsValid)
{
// Validate the user with the membership system.
if (domainProvider.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
List<String> domains2 = new List<String>();
domains2.Add("AD-*****GROUP");
ViewBag.Domains = domains2;
return View(model);
}
}
List<String> domains = new List<String>();
domains.Add("AD-*********GROUP");
ViewBag.Domains = domains;
return View(model);
}
web.config contain the ldap to the on-premises AD and username/password settings:-
<membership>
<providers>
<add name="Domain1ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="Domain1ConnectionString" connectionUsername="********8" connectionPassword="********" attributeMapUsername="sAMAccountName" />
</providers>
</membership>
<connectionStrings>
<add name="Domain1ConnectionString" connectionString="LDAP://ad-*****roup.intra/OU=TDM,DC=ad-***group,DC=intra" />
i do not have any idea from where to start and if i can achive what i am looking for,, and if this is supported or not?

Related

ASP.NET Identity: Cannot sign in on OnApplyRedirect

I'm trying to overrule OnApplyRedirect, and sign in a user if certain query string parameters are present. I use CookieAuthentication.
I can see that AuthenticationResponseGrant in the AuthenticationManager is set correctly, but the user is not signed in and no cookie is set. I'm using the same code as I use in one of my controllers to sign in the user.
var context = HttpContext.Current.GetOwinContext();
var user = UserRepository.FindUserByQueryString(request.Query);
var userManager = new UserManager<OrganizationUser>(new OrganizationUserStore());
var identity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
context.Authentication.SignIn(new AuthenticationProperties { IsPersistent = true }, identity);
context.Response.Redirect(request.Uri);
What on earth am I doing wrong? Thanks!
I wasn't able to sign in the user in the OnApplyRedirect callback. Maybe it's too early in the OWIN pipeline, I don't know.
I ended up redirecting to a new controller method that accepts the same query string parameters, and sign in the user from there, then finally redirecting to the original target url.
If anyone can help me avoid this hack I'd be very grateful!
I was looking for something similar - a solution to security testing on localhost for a distributed identity implementation where the application in question is not the login application. I wanted our team to be able to work with iis express and not have to stand up a full local iis with the login application running to develop other applications.
I tried using middleware and found that it was either too early or too late in the owin pipeline. I also tried your approach above of canceling the redirect and signing in the user when localhost conditions were met but found that I was getting strange errors.
I don't have an answer but wanted to say your solution helped me somewhat. I ended up using logic in the OnApplyRedirect to switch between the centralized login application's login, and a localhost sign-in controller action in the local application.
Here's my implementation in case it helps anyone else going down this road:
OnApplyRedirect = context =>
{
// check for localhost and use the application's little localhost login controller action
if (
//no user is signed in
String.IsNullOrWhiteSpace(context.OwinContext.Authentication.User.Identity.Name)
//and we are on localhost
&& (context.OwinContext.Request.Uri.Host.ToLower() == "localhost")
&& !string.IsNullOrWhiteSpace(System.Configuration.ConfigurationManager.AppSettings["LocalHost_UserName"])
)
{
if (context.OwinContext.Request.PathBase.ToString().Length > 0)
{
//we are on local iis with an application name in the path
//goto /[Application]/Home/LocalhostSignIn
context.RedirectUri = context.OwinContext.Request.PathBase.ToString() + LocalHostSignInRelativePath;
}
else
{
//no path base (pure localhost:port)
//goto /Home/LocalhostSignIn
context.RedirectUri = LocalHostSignInRelativePath;
}
}
else //standard scenario, route them to the normal login application
{
//switch this applications path with the centralized login app's path (declared above in this helper class)
context.RedirectUri = context.RedirectUri.Replace(context.OwinContext.Request.PathBase.ToString(), HomeApplicationRelativePath);
}
context.Response.Redirect(context.RedirectUri);
},
Controller:
[AllowAnonymous]
public ActionResult LocalhostSignIn()
{
Microsoft.Owin.IOwinContext owinContext = ControllerContext.RequestContext.HttpContext.GetOwinContext();
if (
//no user is signed in
String.IsNullOrWhiteSpace(owinContext.Authentication.User.Identity.Name)
//and we are on localhost
&& (owinContext.Request.Uri.Host.ToLower() == "localhost")
&& !string.IsNullOrWhiteSpace(System.Configuration.ConfigurationManager.AppSettings["LocalHost_UserName"])
)
{
IdentityHelper.SignUserIntoContext(owinContext, System.Configuration.ConfigurationManager.AppSettings["LocalHost_UserName"]);
return RedirectToAction("Index");
}
else
{
throw new Exception("Some error message");
}
}
Sign in Logic:
/// <summary>
/// This will sign in a username WITHOUT A PASSWORD!!!!!
/// </summary>
/// <param name="context"></param>
/// <returns>T/F depending on whether or not the UserName passed in was valid</returns>
/// <remarks>
/// Used by WindowsLogin to sign in a domain credential to the Identity (cookie) authentication model
/// Used by LocalhostSignIn to sign in the username located in the LocalHost_UserName for local development (so you don't need a local sign-in application)
/// </remarks>
public static bool SignUserIntoContext(IOwinContext owinContext, string UserName)
{
var userManager = owinContext.GetUserManager<IntranetUserManager>();
IntranetUser intranetUser = userManager.FindByName(UserName);
if (intranetUser != null)
{
var signInManager = owinContext.Get<IntranetSignInManager>();
//sign the user into intranet identity
signInManager.SignIn(intranetUser, false, false);
return true;
}
return false;
}
Web.Config:
<location path="Home/LocalhostSignIn">
<system.web>
<authorization>
<allow users="*" />
<allow users="?" />
</authorization>
</system.web>
<system.webServer>
<security>
<authorization>
<clear />
<add accessType="Allow" users="?" />
<add accessType="Allow" users="*" />
</authorization>
</security>
</system.webServer>
</location>
If anyone has any tips or suggestions or feedback they would be appreciated.

SignalR encrypt QueryString parameters

I am using SignalR on a Asp.net Web Api project.
I am connecting to the hubs from separate Asp.net MVC projects.
Everything works fine until now.
However, i need to implement Authentication on the SignalR Hubs, in order to do this, i simply need a token to be send as QueryString parameter:
// Hub implementation on Asp.Net Web Api project
public class AppHub : Hub
{
public override async Task OnConnected()
{
string token = Context.QueryString["token"];
var validateResult = ValidateRequestService.ValidateToken(token);
Groups.Add(Context.ConnectionId, validateResult.UserName);
base.OnConnected();
}
}
// Javascript implementation on Asp.net MVC project
$.connection.hub.url = 'http://webApiProject.com/signalr';
$.connection.hub.qs = { 'token': '#(ViewBag.SessionToken)' };
This works.
The problem is that i store sensitive information (token) on the client (browser). If a hacker inspects the source code of the page, it can easily see the token key.
Is there any way to encrypt / decrypt the query string parameter so it will be encrypted on the client side?
I can easily encrypt it on the client, but the problem is that it will be sent encrypted to the Web Api server as well.
Would an HttpModule work in this case?
To implement a custom memebership provider implement System.Web.Security.MembershipProvider
Example from one of my projects
public class MembershipProvider : System.Web.Security.MembershipProvider
{
...
public override bool ValidateUser(string username, string password)
{
return DependencyResolver.Current.GetService<IUserManager>().ValidateUser(username, password);
}
}
If you need roles implement role provider System.Web.Security.RoleProvider
public class RoleProvider : System.Web.Security.RoleProvider
{
...
public override string[] GetRolesForUser(string username)
{
var user = dependencyResolver.Current.GetService<IUserManager>().GetUserBy(username);
return user.Roles.Select(r => r.Name).ToArray();
}
}
All other methods can be left unimplemented for basic functionality
In the web config do
<membership defaultProvider="MyProvider" userIsOnlineTimeWindow="20">
<providers>
<remove name="AspNetSqlProvider" />
<add name="MyProvider" type="MyApp.Web.Common.Membership.MembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" passwordFormat="Hashed" applicationName="/" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear />
<add name="MyRoleProvider" applicationName="/" type="MyApp.Web.Common.Membership.RoleProvider" />
</providers>
</roleManager>
You can then login like Forms auth was enabled for example
[HttpPost]
public bool Login([FromBody]CredentialsViewModel credentials)
{
if (Membership.ValidateUser(credentials.Username, credentials.Password))
{
FormsAuthentication.SetAuthCookie(credentials.Username, credentials.Remember);
return true;
}
return false;
}

Can I use Basic Authentication in Umbraco without a Login page

I'm building an ASP.Net MVC web application, which uses Umbraco7, to replace an old WebForms website.
The old WebForms site uses Basic Authentication on some sections of the site (specified at the directory level in IIS), which specify a default Windows domain with its own Active Directory. The browser requests the user ID and password on the appropriate pages, and the code behind retrieves the user information using the System.Web.UI.Page.User.Identity property.
I would like to provide a similar experience on the new Umbraco site.
Examples of MVC sites using Basic Authentication specify the authentication and default domain as attributes on the Controller methods, http://www.asp.net/mvc/tutorials/older-versions/security/authenticating-users-with-windows-authentication-cs.
Umbraco doesn't appear to provide individual controller methods for its content pages, and I've not found any Umbraco authentication examples that use Basic Authentication and rely on the browser to retrieve the credentials.
Is it possible to use Basic Authentication on an Umbraco content page and retrieve the credentials using the browser?
Updated Answer:
I stumbled on this question several months after posting the original answer, Combining Forms Authentication and Basic Authentication. I have not tested this solution, since that ship has sailed, but it looks promising.
Original Answer:
From what I can tell, the answer to this question is "no". You cannot modify the header of a Umbraco content page, so you can't tell the browser to authenticate itself against a given LDAP server.
However, I was able to use forms authentication to behave in the same way (authenticate against Active Directory and authorize against my database). Below I've included all the code needed to get this authentication to work in Umbraco.
Login Page
The login page is just an Umbraco View with the following Partial View and Surface Control added to it using #Html.Action("MemberUmbLogin", "MemberUmbLoginSurface")
#model CustomUmbraco.Models.MemberUmbLoginModel
#if (User.Identity.IsAuthenticated)
{
<p>Logged in: #User.Identity.Name</p>
<p>#Html.ActionLink("Log out", "MemberUmbLogout", "MemberUmbLoginSurface")</p>
}
else
{
using (Html.BeginUmbracoForm("MemberUmbLogin", "MemberUmbLoginSurface"))
{
#Html.EditorFor(x => Model)
<input type="submit" />
}
<p>#TempData["Status"]</p>
}
Login Model
public class MemberUmbLoginModel
{
public string Username { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Surface Controller
public class MemberUmbLoginSurfaceController : SurfaceController
{
//
// GET: /MemberUmbLogin/
[HttpGet]
[ActionName("MemberUmbLogin")]
public ActionResult MemberUmbLoginGet()
{
return PartialView("MemberUmbLogin", new MemberUmbLoginModel());
}
[HttpGet]
public ActionResult MemberUmbLogout()
{
Session.Clear();
FormsAuthentication.SignOut();
return Redirect("/");
}
[HttpPost]
[ActionName("MemberUmbLogin")]
public ActionResult MemberUmbLoginPost(MemberUmbLoginModel model)
{
string returnUrl = GetValidReturnUrl(Request.UrlReferrer);
if (Membership.ValidateUser(model.Username, model.Password))
{
FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && !String.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToCurrentUmbracoPage();
}
TempData["Status"] = "Invalid username or password";
return RedirectToCurrentUmbracoPage();
}
private static String GetValidReturnUrl(Uri uri)
{
string returnUrl = null;
if (uri != null && !String.IsNullOrWhiteSpace(uri.PathAndQuery) && uri.PathAndQuery.StartsWith("/") &&
!uri.PathAndQuery.StartsWith("//") && !uri.PathAndQuery.StartsWith("/\\"))
{
returnUrl = uri.PathAndQuery;
}
return returnUrl;
}
}
I'm using a custom MembershipProvider with the standard Umbraco Role Provider. I rely on the MembershipProvider to update the roles for a member based on my non-Umbraco database whenever they log in. The MembershipProvider then updates the member with the appropriate groups.
Note: Because I'm using Umbraco's role provider, I need to add the roles from my non-Umbraco database to Umbraco as "Member Groups".
Web.config
<!-- Membership Provider -->
<membership defaultProvider="CustomMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add name="CustomMembershipProvider" type="CustomUmbraco.MembershipProviders.CustomMembershipProvider" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="NetIDAlias" passwordFormat="Hashed" />
<add name="UmbracoMembershipProvider" type="Umbraco.Web.Security.Providers.MembersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed" />
<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
</providers>
</membership>
<!-- Role Provider -->
<roleManager enabled="true" defaultProvider="UmbracoRoleProvider">
<providers>
<clear />
<add name="UmbracoRoleProvider" type="Umbraco.Web.Security.Providers.MembersRoleProvider" />
</providers>
</roleManager>
MembershipProvider
public class CustomMembershipProvider : MembershipProvider
{
private String GetDomainFreeName(String fullName)
{
return fullName.Contains("\\") ? fullName.Substring(fullName.IndexOf("\\") + 1) : fullName;
}
public override bool ValidateUser(string username, string password)
{
DirectoryEntry directoryEntry = new DirectoryEntry(ConfigurationManager.ConnectionStrings["LDAPConnection"].ConnectionString, username, password);
DirectorySearcher searcher = new DirectorySearcher(directoryEntry);
String domainFreeName = GetDomainFreeName(username);
searcher.Filter = String.Format("(&(objectClass=user)(SAMAccountName={0})(!msExchUserAccountControl=2))", domainFreeName);
SearchResult result;
try
{
result = searcher.FindOne();
}
catch (COMException)
{
return false; // authentication failed
}
if (result != null)
{
NotReallyARoleProvider provider = new NotReallyARoleProvider();
provider.UpdateUserRoles(domainFreeName);
Member m = Member.GetMemberFromLoginName(domainFreeName);
Member.AddMemberToCache(m);
return true;
}
return false;
}
}
UpdateUserRoles methods
public void UpdateUserRoles(String username)
{
var groups = this.GetRolesForUser(username); // this is the method that gets the roles for your user.
this.RemoveUsersFromRoles(new[] { username }, this.GetAllRoles());
this.AddUsersToRoles(new[] { username }, groups);
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
foreach (String username in usernames)
{
Member m = Member.GetMemberFromLoginName(username);
if (m == null)
{
m = Member.MakeNew(username, MemberType.GetByAlias("Member"), new User(0));
m.LoginName = username;
}
roleNames.ForEach(group => m.AddGroup(MemberGroup.GetByName(group).Id));
m.Save(true);
}
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
foreach (String username in usernames)
{
Member m = Member.GetMemberFromLoginName(username);
if (m == null)
{
m = Member.MakeNew(username, MemberType.GetByAlias("Member"), new User(0));
m.LoginName = username;
}
roleNames.ForEach(group => m.RemoveGroup(MemberGroup.GetByName(group).Id));
m.Save(true);
}
}
Finally, the reason I don't use the ActiveDirectoryMembershipProvider is because I couldn't get sufficient permissions to modify accounts.
This solution is far from perfect, but it works for me. If you run into an issue where you log in to a page but it acts as though you're not in the correct group, OR if you remove a member from the Umbraco interface and they still show up when you call Member.GetMemberFromLoginName(username), then you may have to replace your Member saving code in your Provider with the following line once.
ApplicationContext.Current.Services.MemberService.DeleteMembersOfType(MemberType.GetByAlias("Member").Id);
That will clear out any members that are stored in the ethereal repository.
With all this code in place, users can select the groups they want to have access to content pages in the Umbraco backoffice, like normal.

LDAP Server is not available

I am using LDAP for User Authentication in MVC.My code goes below as follows:
public ActionResult Login(LoginViewModel model, string returnUrl)
{
bool validation;
try
{
LdapConnection ldc = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
NetworkCredential nc = new NetworkCredential(model.UserName, model.Password, "XXXXXXX");
ldc.Credential = nc;
ldc.AuthType = AuthType.Negotiate;
ldc.Bind(nc); // user has authenticated at this point, as the credentials were used to login to the dc.
validation = true;
return RedirectToAction("Index", "Home");
//validation = true;
}
catch (LdapException)
{
validation = false;
}
return View(model);
}
but i am getting an error as "LDAP server not available"
Web.Config:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="10"/>
</authentication>
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
<providers>
<clear />
<add name="AspNetActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider,System.Web, Version=2.0.0.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
attributeMapUsername="sAMAccountName" />
</providers>
</membership>
<add name="ADConnectionString" connectionString="LDAP://XXXXXXX:389/DC=XXXX,DC=XXXX" />
You did not set the path to the LDAP server (currently it is null)
LdapConnection ldc = new LdapConnection(
new LdapDirectoryIdentifier((string)null, false, false)
);
To debug, get rid of try..catch and see where exactly the error comes from. You might need to verify the path with your network administrator or use any tool like LDAP Browser where you could see if path and credentials would work.
Also, make sure that the way you want to authenticate is correct. If this is an intranet application then it might be that you could setup integrated Windows authentication which will not require any custom login process and can be configured on IIS.

How to Override createUser() ASP .NET Membership method to display custom error message?

How to Override createUser() Membership method to display custom error message when password check fails??
I Used the Web Site Administration Tool, which provides a wizard-like interface for creating new users. (To start this tool, click ASP.NET Configuration on the Website menu in the Microsoft Visual Studio)
Web.Config file:
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider" type="BlueDDApp.Controllers.MyMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" minRequiredPasswordLength="8" minRequiredNonalphanumericCharacters="0" passwordStrengthRegularExpression="^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])|(?=.*[a-z])(?=.*[A-Z])(?=.*[!%,.;:])|(?=.*[a-z])(?=.*[0-9])(?=.*[!%,.;:])|(?=.*[A-Z])(?=.*[0-9])(?=.*[!%,.;:])$" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
Custom Membership class::
public class MyMembershipProvider : SqlMembershipProvider
{
public MyMembershipProvider()
{
//Membership.ValidatingPassword += new MembershipValidatePasswordEventHandler(OnValidatePassword);
ValidatingPassword += ValidatePassword;
}
/* public override MembershipUser CreateUser( string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
ValidatingPassword += ValidatePassword;
return base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status);
}*/
void ValidatePassword(object sender, ValidatePasswordEventArgs e)
{
Regex check = new Regex("^(?i)(?!.*" + e.UserName + ").*$");
if (!check.IsMatch(e.Password))
{
e.FailureInformation = new HttpException("blah blah");
e.Cancel = true;
}
}
}
If you are using asp:CreateUserWizard control, which I presume you are ( it will connect to a membership provider from your web.config ), then :
In Design mode, if you click on this control, in the top right corner you have an icon, sort of an arrow, and there you can choose "Customize Create User Step" option. This will transform the control, expanding it into a separate controls that are used inside. Now you can access error message ( Literal control ) and change it to display static message, or display dynamically changing messages from code.
You can also add events to the CreateUserWizard like CreatingUser, CreateUserError and CreatedUser which will let you customize the behavior and how the creation is being used even more.
Here is a great sample about custom MembershipUser:
Sample Membership Provider Implementation

Resources