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;
}
Related
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.
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() 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
I have looked at nearly every single WCF Rest PUT/POST issues on SO here and have still been unable to determine why I am unable to PUT or POST to my web service but am able to call a test GetTime method via GET.
This particular service exchanges custom credential information (username, password and some other information) for a token. This token information is encrypted and added to the header of subsequent requests to other web services so that username/passwords don't have to be passed around everywhere.
All web service calls are still treated as stateless, but require this auth header information rather username/passwords to access secured service operations.
Contract
[ServiceContract(Namespace = "")]
public interface IUserService
{
[WebInvoke(Method = "PUT", UriTemplate = "users/{username}/session")]
[WebHelp(Comment = "Creates a new session for the specified username")]
[OperationContract]
AuthToken PutSession(string username, CustomCredential credential);
...
[WebInvoke(Method = "GET", UriTemplate = "time")]
[WebHelp(Comment = "Test method; returns the time")]
[RequireAuthToken]
[OperationContract]
DateTime GetTime();
}
Service Impelementation
public class UserService : IUserService
{
#region UserService Members
public AuthToken PutSession(string username, CustomCredential credential)
{
// ...
}
public DateTime GetTime()
{
return DateTime.Now;
}
}
Test Code
[Fact]
public void When_Authenticated_And_Getting_Time_Expect_200_Status()
{
// arrange
using (var client = Get_HttpClient_With_AuthHeaders()) {
// act
var result = client.Get("time");
// assert
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
}
The aforementioned GetTime works (just a test method I added).
[Fact]
public void When_PuttingSession_Expect_AuthToken_Is_Returned()
{
// arrange
using (var client = Get_HttpClient_With_No_Auth_Headers()) {
var cred = new CustomCredential("test", "password", 1);
var content = HttpContentExtensions.CreateDataContract<CustomCredential>(cred);
// act
HttpResponseMessage response = client.Put("users/test/session", content);
// assert
response.EnsureStatusIsSuccessful();
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var authToken = response.Content.ReadAsDataContract<AuthToken>();
Assert.NotNull(authToken);
}
}
The above put returns a MethodNotAllowed (405).
Just as another test I added a Mex endpoint to the service, created a new console app and added a 'service reference' to this service. Using the generated client proxy code, I was able to something similar to....
IUserServiceClient client = new IUserServiceClient();
CustomCredential cred = ...
AuthToken token = client.PutSession("test_username", cred);
What this suggests is that...
The web service is hosted in IIS correctly
I am able to consume the service using SOAP client proxy generated code
I am able to consume GET requests via more friendly HttpClient / rest toolkit
I have tested this get in a browser and it works
For some reason put and other related methods (post, delete. etc) do not work via rest toolkit
Have no idea what is causing this one.
EDIT
I also noticed that IIS must have created this web.config in the root of the website where the services are hosted which would seem to be allowing the main HTTP verbs.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<verbs>
<add verb="PUT" allowed="true" />
<add verb="GET" allowed="true" />
<add verb="POST" allowed="true" />
<add verb="DELETE" allowed="true" />
</verbs>
<fileExtensions>
<add fileExtension=".svc" allowed="true" />
</fileExtensions>
</requestFiltering>
</security>
</system.webServer>
</configuration>
I have also checked in IIS 7 the handler mappings for *.svc and checked that 'all verbs' are enabled.
Check that related handler for .svc in IIS is allowed to process HTTP POST, PUT, DELETE. In case of problems with only PUT and DELETE check that your virtual directory is not configured for WebDAV (I think it is some HTTP module).
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?