Adding features to ServiceStack auth provider - servicestack

I am evaluating ServiceStack using OrmLite. The built in Auth service, along with Session and Cache are so much better than ASP.NET membership provider.
However, out of the box the Auth Service does not provide some of the features required for apps we want to build like:
Change password
Locking of account after 3 unsuccessful logon attempts
Disabling user accounts
Password reminder question and answer
Audit log of log on attempts
Do I need to build custom auth provider or is there something out there which already does provides this functionality?
Many thanks!

I'm just starting to implement a password reset and can see two ways of achieving it (I've not tested - or even tried - either yet):
1.Create a class that inherits from Registration and handles PUT. It should then be possible to call the UpdateUserAuth method of the registration class which would change the password. The problem - for me - here is that the put validation requires username and password to be specified, not just one (We only use email as an identifier). This could be worked around by turning the validation feature off.
2.Create a password reset service that does what UpdateUserAuth does.
var session = this.GetSession();
var existingUser = UserAuthRepo.GetUserAuth(session, null);
if (existingUser == null)
{
throw HttpError.NotFound("User does not exist");
}
var newUserAuth = ToUserAuth(request);
UserAuthRepo.UpdateUserAuth(newUserAuth, existingUser, request.Password);
Obviously need to add some appropriate validation in.
UPDATED
I've put my change password reminder/reset service up as a gist (My first gist!)

here's what I did, works well. - I realise the "new" is a code-smell, just inject it :)
private int LoginAttempts = 0;
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = authService.TryResolve<IUserAuthRepository>();
if (authRepo == null)
{
Log.WarnFormat("Tried to authenticate without a registered IUserAuthRepository");
return false;
}
var session = authService.GetSession();
UserAuth userAuth = null;
if (authRepo.TryAuthenticate(userName, password, out userAuth))
{
session.PopulateWith(userAuth);
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserOAuthProviders(session.UserAuthId)
.ConvertAll(x => (IOAuthTokens)x);
return true;
}
else
{
LoginAttempts++;
if (LoginAttempts >= 3)
{
ServiceStack.ServiceInterface.Service s = new Service();
s.Db.ExecuteSql("update [User] set AccountLocked = 'true' where Email='" + userName + "'");
}
authService.RemoveSession();
return false;
}
}
and I hope the mod_from_hell manages to leave this alone!!!

Related

Get servicestack roles from http context

I have been using the following authorization filter for hangfire to link it to ServiceStack auth:
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development")
{
return true;
}
var coreContext = context.GetHttpContext();
var httpContext = coreContext.ToRequest();
var session = httpContext.GetSession();
if (session != null && session.IsAuthenticated && session.Roles != null && session.Roles.Contains("Admin"))
{
return true;
}
return false;
}
}
context.GetHttpContext() returns DefaultHttpContext. The session has the correct username and ID but it doesn't contain the users roles.
I am using credentials login with the ServiceStack built in login.html on this project. On my other projects I am using JWT auth so I think that's maybe why this doesn't work now.
How can I get the user roles from the context when using credential auth?
Edit:
Looking in database at the saved session I can also see it has no roles "roles":[]. I am using distinct roles table and can confirm there is an entry for this user with Admin role. I tried manually adding roles to user auth table column but that didn't change anything.
Use HasRole() to validate whether a User is in a Role, e.g:
return session.HasRole(role, httpContext.TryResolve<IAuthRepository>());

OWIN with LDAP Authentication

Here is my scenario. I have an MVC 5 application that uses Owin as an authentication mechanism. The default template calls the SignInManager.PasswordSignInAsync in the Login action which I would like to overwrite to use LDAP to validate the user instead of looking into the database.
I am able to do the validation via:
PrincipalContext dc = new PrincipalContext(ContextType.Domain, "domain.com", "DC=domain,DC=com", "user_name", "password");
bool authenticated = dc.ValidateCredentials(userName, password);
Then I can retrieve the UserPrincipal using:
UserPrincipal user = UserPrincipal.FindByIdentity(dc, IdentityType.SamAccountName, userName);
However, I am stuck here and I am not sure how to continue with signing in the user. The goal is that after I sign in the user, I would have access to User.Identity including all the roles the user is in. Essentially, the app should behave as if it uses Windows Authentication, but the credentials are provided by the user on the Login page.
You would probably ask why not user Windows Authentication directly. The app will be accessed from the outside of the network, but the requirements are to use AD authentication and authorization. Hence my predicament.
Any suggestions are highly appreciated.
Thank you.
After many hours of research and trial and error, here is what I ended up doing:
AccountController.cs - Create the application user and sign in
ApplicationUser usr = new ApplicationUser() { UserName = model.Email };
bool auth = await UserManager.CheckPasswordAsync(usr, model.Password);
if (auth)
{
List claims = new List();
foreach (var group in Request.LogonUserIdentity.Groups)
{
string role = new SecurityIdentifier(group.Value).Translate(typeof(NTAccount)).Value;
string clean = role.Substring(role.IndexOf("\\") + 1, role.Length - (role.IndexOf("\\") + 1));
claims.Add(new Claim(ClaimTypes.Role, clean));
}
claims.Add(new Claim(ClaimTypes.NameIdentifier, model.Email));
claims.Add(new Claim(ClaimTypes.Name, model.Email));
ClaimsIdentity ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = false,
ExpiresUtc = DateTime.UtcNow.AddDays(7),
}, ci);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid login credentials.");
return View(model);
}
IdentityConfig.cs (CheckPasswordAsync) - Authenticate against LDAP
public override async Task CheckPasswordAsync(ApplicationUser user, string password)
{
PrincipalContext dc = new PrincipalContext(ContextType.Domain, "domain", "DC=domain,DC=com", [user_name], [password]);
bool authenticated = dc.ValidateCredentials(user.UserName, password);
return authenticated;
}
Global.asax - if you are using the Anti Forgery Token in your login form
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
At this point, you will are logged in and can access the User.Identity object. You can also mark controllers and actions with [Authorize(Roles = "some_role"]
It turned out that it was easier than I thought, it is just that not much is really written on the topic (at least I could not find anything).
Also, this code presumes that you are running the app from a server which has access to the Domain Controller on your network. If you are on a DMZ server, you need to discuss this strategy with your network admin for other options.
I hope this saves you some time. I am also eager to hear what the community thinks of this. Maybe there is a better way of handling this situation. If so, please share it here.
Thanks.
Daniel D.

Storing hash of username using ASP.NET identity

I'm writing an application which needs to have personally identifiable information removed/absent at all times from the database. Given that someone may use their real name in their username, and that an email address may be present in their AspUserIdentity records, I have decided one solution might be to hash these values. In simple terms: when someone logs in with a username, I hash the username they entered and see if that hash exists in the database; if it does, then I log them in. This is easy to do and works just fine by modifying the Login and Register methods in the AccountController. But now I am left with no knowledge of the entered username...
I could just store the username in session, but that seems jankety. What I'd like to do is to update the cookie that gets sent down upon successful login to use the username they entered (and not the hashed value stored in the DB). That way User.Identity.GetUserName() returns the plain text username (and not the hashed username). To the client the process ought to be transparent (and to me as the programmer too).
The question is: how? What's the best place to do this? I'm still relatively green when it comes to the latest ASP.NET Identity stuff. I see in Startup.Auth there's a lot of juicy stuff related to cookies, but I don't see anywhere I can modify the cookie itself upon login and prior to it being sent down.
Is all of this deep within Owin itself?
Thanks in advance,
When user logs in and you compare the hash of username, you can add their real username as a claim to the identity. This is serialised into cookie and available with the user on every request, but not persisted in a DB:
public async Task SignIn(string userName, string password, bool rememberMe)
{
var hashedUsername = getMyHash(username)
var loggedInUser = await userManager.FindAsync(hashedUsername, password);
if (loggedInUser == null)
{
// failed to login
return FailedToLogin(); // whatever you do there
}
// Ok, from now on we have user who provided correct username and password.
// and because correct username/password was given, we reset count for incorrect logins. This is for user lockout
await userManager.ResetAccessFailedCountAsync(loggedInUser.Id);
if (!loggedInUser.EmailConfirmed)
{
return EmailIsNotConfirmed(); // email is not confirmed - display a message
}
if (await userManager.IsLockedOutAsync(loggedInUser.Id))
{
return UserLockedOut(); // user is locked out - display a message
}
var identity = await userManager.CreateIdentityAsync(loggedInUser);
identity.AddClaim(new Claim("OriginalUsername", originalUsername));
var authenticationManager = context.GetOwinContext().Authentication;
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = rememberMe }, identity);
//TODO redirect to a home page
}
Then when you need to display an actual username, not a hash do this:
public static String GetOriginalUsername(this IPrincipal principal)
{
if (principal == null)
{
return String.Empty;
}
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
return String.Empty;
}
var originalUsernameClaim = principal.Claims.SingleOrDefault(c => c.Type == "OriginalUsername");
if (originalUsernameClaim == null)
{
return String.Empty;
}
return originalUsernameClaim.Value;
}
And call this method on User.GetOriginalUsername() in *.cshtml files or in Controllers. Or HttpContext.Current.User.GetOriginalUsername() if you need it somewhere else.

How to get and display the email using the Facebook provider in ASP.NET Identity and MVC 5

UPDATE: PLEASE SEE THIS POST: https://stackoverflow.com/a/20379623/687549
Been reading I think almost all questions on SO about external providers and how to get extra info/data/metadata/claims/orwhateveryouwannacallit in particular the email address which many use as the username on modern websites.
So the problem that I was facing was that I wanted to retrieve the email from the Facebook provider with as little code as possible. I thought to myself; the new ASP.NET Identity framework must have this buil-in and can do this with probably just a couple of lines of code. I searched and all I could find was these imensely large chunks of code and I thought to myself: there has got to be another more simpler way. And here it is, as an answer in this questionthread.
I managed to get this working with both Facebook and Google but what I'm concerned about is wheather or not I'm doing this right without any consequenses somewhere else in the code.
For instance: Do you really only need to specify the Scopes.Add("email") for it all to work or why haven't I been able to find more info about this on the interweb?
UPDATE: PLEASE SEE THIS POST: https://stackoverflow.com/a/20379623/687549
Startup.Auth.cs:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "myAppId",
AppSecret = "myAppSecret"
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
AccountController (default mvc 5 template app stuff)
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// These next three lines is how I get the email from the stuff that gets returned from the Facebook external provider
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
// Populate the viewmodel with the email
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = email });
}
}
I have the same problem. You need to edit and add this code to ExternalLoginCallback in the AccountController
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// added the following lines
if (loginInfo.Login.LoginProvider == "Facebook")
{
var identity = AuthenticationManager.GetExternalIdentity(DefaultAuthenticationTypes.ExternalCookie);
var access_token = identity.FindFirstValue("FacebookAccessToken");
var fb = new FacebookClient(access_token);
dynamic myInfo = fb.Get("/me?fields=email"); // specify the email field
loginInfo.Email = myInfo.email;
}
Note the code dynamic myInfo = fb.Get("/me?fields=email"); this will work for facebook app with version 2.4, but for old version you can write this
dynamic myInfo = fb.Get("email");

NUnit & MVC4 SimpleMembership

I'm having a really difficult time creating unit tests for my MVC4 application, using NUnit. Right now I'm just focusing on this one test in particular, which logs a user in OR creates them if they don't exist, and then logs them in. Here is the test :
[Test]
public void LoginValidUser()
{
//Start up the DB connection
App_Start_DB();
//Setup our default test user
string UserName = "LocalTestUser#penrouse.com";
string Password = "books";
bool LoginWorked = false;
//check and see if our test user exists
if (!WebSecurity.UserExists(UserName))
{
//If not, create them
WebSecurity.CreateUserAndAccount(UserName, Password, new
{
Name = "Local Test User",
IsPromotional = true,
IsAllowShare = true
});
//Log them in
LoginWorked = WebSecurity.Login(UserName, Password);
}
else
{
//This user already exists, just log them in
LoginWorked = WebSecurity.Login(UserName, Password);
}
Assert.IsTrue(LoginWorked);
Trace.WriteLine("Login Valid User Result : " + LoginWorked.ToString());
}
The problem is that every time I attempt WebSecurity.Login(), I get a null reference exception, and the stack trace points to :
System.Web.Security.FormsAuthentication.SetAuthCookie(String userName, Boolean createPersistentCookie, String strCookiePath);
Calling that method directly before the login attempt the does not change the behavior. Thus, I have two questions :
Is there a better way to go about testing these parts of SimpleMembership?
If not, is there a good way to override or mock out the AuthCookie so that the login will work when tested in this way?
Any help\insight would be hugely appreciated.

Resources