I have a application with the following Layout. In the Shared Views Folder I have, _Layout.cshtml, _SideNav.cshtml and _CurrentUser.cshtml.
In the _Layout.cshtml I have:
#{ Html.RenderPartialIf("_SideNav", Request.IsAuthenticated); }
In the _SideNav.cshtml I have:
#{ Html.RenderPartial("_CurrentUser"); }
In the _CurrentUser.cshtml I have:
<div class="login-info">
<span>
<a href="javascript:void(0);" id="show-shortcut" data-action="toggleShortcut">
<img src="~/content/img/avatars/sunny.png" alt="me" class="online" />
<span>#User.Identity.Name</span>
<i class="fa fa-angle-down"></i>
</a>
</span>
</div>
We use FormsAuthentication to authenticate a user. We are not using the standard Identity authentication which ships with ASP.Net MVC 5 because we are using a LDAP Server.
FormsAuthentication.SetAuthCookie(username, isPersistent);
.....
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(username, "Forms"), roles);
We use the username in the cookie so that we can easily get information from the LDAP server.
Problem: #User.Identity.Name returns that username. But I need to display the full name of the user. I have access to the full name when we authenticate. but not sure how to use it.
How can I pass the FullName value from the AccountController to the _CurrentUser.cshtml partial view? Kind of like a Global Container like #User.Identity with more attributes that can be set.
You can use something like this
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
viewModel.Email,
YOUR_ISSUE_DATE,
YOUR_EXPIRE_DATE,
viewModel.RememberMe,
JsonConvert.SerializeObject(user),
FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie authcookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
Response.Cookies.Add(authcookie);
You can try something similar to-->
public static MyAuthenticationTicket GetIMyUserTicket()
{
//Get custom user data object from forms auth cookie
MyAuthenticationTicket result= null;
if (HttpContext.Current.User == null)
return null;
if (!HttpContext.Current.Request.IsAuthenticated)
return null;
FormsIdentity ident = (FormsIdentity)HttpContext.Current.User.Identity;
if (ident == null)
return null;
FormsAuthenticationTicket ticket = ident.Ticket;
if (ticket == null)
return null;
if (!FormsAuthentication.CookiesSupported)
{
//If cookie is not supported for forms authentication, then the
//authentication ticket is stored in the Url, which is encrypted.
//So, decrypt it
ticket = FormsAuthentication.Decrypt(ident.Ticket.Name);
}
string userDataString = ticket.UserData;
if(!String.IsNullOrEmpty(userDataString))
result= new MyAuthenticationTicket(userDataString);
return result;
}
Use ClaimsIdentity.
When the user login to your app add his name to your identity.
ClaimsIdentity claims = new ClaimsIdentity();
claims.AddClaim(new Claim("name", "value"));
Then create a extension method to get your name
public static string GetName(this IPrincipal principal)
{
return ((ClaimsIdentity)principal.Identity).Claims.Where(x => x.Type == "name").FirstOrDefault().Value;
}
Now use it as #User.GetName()
Update:
Or in you login controller use UserManager to get the user identity.
Like this:
ClaimsIdentity claims = UserManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
claims.Add(new Claim("name" , "value"));
AuthenticationManager.SignIn(claims);
Related
So here's a break down of my issue in steps:
The user logs in with Google.
On the login callback, information about the user is gathered
Roles are assigned based on the user
A FormsAuthenticationTicket is created which passes the user/roles to the Application_PostAuthenticateRequest in Global.asax
In that request a GenericPrinciple is created from the authentication ticket and roles
HttpContext.Current.User is set to the above variable
Now my question is, now that I set who the current user is that is using the website, how can I reference them? After the post authenticate complete, I check the current user and it's null. Should I be setting the principle to a different variable other than HttpContext.Current.User?
Callback
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
...
// Get roles for current user
string roles = "bob,adminbob,cthulu";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
loginInfo.Email,
DateTime.Now,
DateTime.Now.AddMinutes(30), // value of time out property
false,
roles,
FormsAuthentication.FormsCookiePath);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
Response.Cookies.Add(authCookie);
...
}
Post Authenticate in Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) {
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null) {
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] userRoles = authTicket.UserData.Split(new Char[] { ',' });
GenericPrincipal userPrincipal = new GenericPrincipal(new GenericIdentity(authTicket.Name), userRoles);
HttpContext.Current.User = userPrincipal; //How do I reference this in the program?
}
}
Nevermind I figured out why it was returning blank. When I was calling the method to check the Name, the Identity wasn't authenticated yet. If you try to get the name without it being authenticated, it returns blank.
So I just simply checked with:
HttpContext.Current.User.Identity.IsAuthenticated
How to use claims? For example, I want to set access to each page (resource) for each user. I understand, I can do it using roles, but as I understand, claim-based is more effectively. But when I try to create a claim, I see the following method:
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "test role"));
first parameter of constructor of Claim class get ClaimTypes enum, which has many "strange" members like Email, Phone etc. I want to set that this claim and then check this claim to have access to certain resource. I'm on wrong way? How to do it?
From the code above, I am assuming you have already added the claim in startup class on authenticated of your provider as below.
context.Identity.AddClaim(new Claim("urn:google:name", context.Identity.FindFirstValue(ClaimTypes.Name))); // added claim for reading google name
context.Identity.AddClaim(new Claim("urn:google:email", context.Identity.FindFirstValue(ClaimTypes.Email))); // and email too
Once you have added the claims in startup, when the request is actually processed check if its a callback and if yes, read the claims as below(in IHttpHandler).
public void ProcessRequest(HttpContext context)
{
IAuthenticationManager authManager = context.GetOwinContext().Authentication;
if (string.IsNullOrEmpty(context.Request.QueryString[CallBackKey]))
{
string providerName = context.Request.QueryString["provider"] ?? "Google";//I have multiple providers so checking if its google
RedirectToProvider(context, authManager, providerName);
}
else
{
ExternalLoginCallback(context, authManager);
}
}
If its 1st call redirect to provider
private static void RedirectToProvider(HttpContext context, IAuthenticationManager authManager, string providerName)
{
var loginProviders = authManager.GetExternalAuthenticationTypes();
var LoginProvider = loginProviders.Single(x => x.Caption == providerName);
var properties = new AuthenticationProperties()
{
RedirectUri = String.Format("{0}&{1}=true", context.Request.Url, CallBackKey)
};
//string[] authTypes = { LoginProvider.AuthenticationType, DefaultAuthenticationTypes.ExternalCookie };
authManager.Challenge(properties, LoginProvider.AuthenticationType);
//without this it redirect to forms login page
context.Response.SuppressFormsAuthenticationRedirect = true;
}
And finally read the claims you get back
public void ExternalLoginCallback(HttpContext context, IAuthenticationManager authManager)
{
var loginInfo = authManager.GetExternalLoginInfo();
if (loginInfo == null)
{
throw new System.Security.SecurityException("Failed to login");
}
var LoginProvider = loginInfo.Login.LoginProvider;
var ExternalLoginConfirmation = loginInfo.DefaultUserName;
var externalIdentity = authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
var pictureClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("picture"));
var pictureUrl = pictureClaim.Value;
LogInByEmail(context, email, LoginProvider); //redirects to my method of adding claimed user as logged in, you will use yours.
}
Claim doesn't set permission. It's used to verify you that "you are who you claim to be you are". These claims are identified by issuer, usually a 3rd party. See for example this article for description.
So, you should define which claims are necessary (who user should be) in order to access a certain page. Otherwise, using claim-based authorization will be same as using identity based or role based.
Because of the requirements of my project, I'm wanting to provide custom authenticate for my MVC controller actions. Therefore, I will not be using SetAuthCookie().
Intially I set a cookie as follows;
string userData = EncDec.MakeString(user.Email + "|" + user.UserId);
//the Cookie and FormsAuthenticationTicket expiration date/time is the same
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
user.UserName, // authenticated username
DateTime.Now, // issueDate
cookieExpiry, // expiryDate
false, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
//create the cookie
HttpCookie cookie = new HttpCookie("ADV_" + Extensions.ControllerExtensionMethods.GetGuid(this), encryptedTicket);
cookie.Secure = true;
cookie.HttpOnly = true;
cookie.Expires = cookieExpiry;
Response.Cookies.Add(cookie);
The HttpCookie is being saved in the client browser with a encrypted FormsAuthenticationTicket.
Then within my controller actions, whenever I need to check and verify that the user is authenticated I call this method;
public static FormsAuthenticationTicket IsAuthenticated(string guid)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies["ADV_" + guid];
if (cookie != null)
{
string encryptedTicket = cookie.Value;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encryptedTicket);
if (!ticket.Expired)
{
//if the user is authenticated and the cookie hasn't expired we increase the expiry of the cookie - keep alive
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
//create a new ticket based on the existing one
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(
ticket.Version, // ticket version
ticket.Name, // authenticated username
ticket.IssueDate, // issueDate
cookieExpiry, // expiryDate, changed to keep alive if user is navigating around site
false, // true to persist across browser sessions
ticket.UserData, // can be used to store additional user data
ticket.CookiePath); // the path for the cookie
string newEncryptedTicket = FormsAuthentication.Encrypt(newTicket);
//keep alive
HttpCookie newCookie = new HttpCookie("ADV_" + guid, newEncryptedTicket);
newCookie.Secure = true;
newCookie.HttpOnly = true;
newCookie.Expires = cookieExpiry;
HttpContext.Current.Response.Cookies.Set(newCookie);
return newTicket;
}
}
return null;
}
Every time the user is re-authenticated, I am increasing the time out of when the cookie will expire, so that the login is keep alive.
Everything seems to work fine, and the users are correctly authenticated, and if they aren't authenticated I redirect them to a login page, and they can't access methods if they aren't authenticated either.
My questions are:
Is this way of dealing with the authentication secure.
Is there anything I should be aware of, in terms of a security risk.
Thanks.
You basically need to look at creating a Custom Authentication Attribute.
I'm not going to provide the actual implementation here, but this will put you on the right path.
Here's the basic representation :-
public class GoogleAuthAttribute : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
IIdentity ident = filterContext.Principal.Identity;
if (!ident.IsAuthenticated || !ident.Name.EndsWith("#google.com"))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
if (filterContext.Result == null || filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "GoogleAccount"},
{"action", "Login"},
{"returnUrl", filterContext.HttpContext.Request.RawUrl}
});
}
}
}
I've took this from the Apress Book i'm currently reading on MVC5. If OnAuthentification fails, you set the Result property of the AuthenticationContext, this is then passed to the AuthenticationChallengeContext, where you can add your challenge code.
In my example, the user is redirected to the login page.
All you need t do, is place this AuthentificationAttribute on the Action Methods you require.
You should be able to build in or work your custome security code in to this.
You should really ask yourself if its a good idea to be adding custom security measures, as it can lead to more problems that you want.
I have the function AffiliateLogin in a controller that sets the Principal.
the row principal.User = user; is actually the one storing the Principal.
But after I redirect to another controller, and test my AuthorizeWithRolesAttribute attribute, the principal is reset.
This is one second after the login, you can see the red arrow:
this is the function that stores it.
What am I doing wrong?
Thanks
public JsonResult AffiliateLogin(string email, string password)
{
if (ModelState.IsValid)
{
Affiliate user = api.GetUserByCredencials<Affiliate>(email, password);
if (user != null)
{
IIdentity identity = new UserIdentity(true,user.Email);
UserPrincipal principal = new UserPrincipal(identity, new string[] {"Affiliate"});
principal.User = user;
HttpContext.User = principal;
return Json("Login success");
}
}
return Json("Fail To Login");
}
The principal property won't survive between web requests. You had to set it again in the next request after redirection.
If your doing doing custom authentication/forms authentication you should call
FormsAuthentication.SetAuthCookie
The next http from the browser with that cookie , Asp.net will process the cookie and set
the current claims principal. So you can check
var principal = ClaimsPrincipal.Current; //normally this reverts to Thread.CurrentPrincipal,
Here is a good place to learn a bit more
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsprincipal.current
I have this application that uses custom methods to register and loggin users using FormsAuthentication. The server where this is hosted has a policy of restarting the sessions every 15 minutes and when that happens all my users get logged out. The code to loggin a user is:
var user = this.accountRepo.GetUser(id);
// Create the forms authentication cookie
var cookieValue = user.name;
HttpCookie cookie = FormsAuthentication.GetAuthCookie(cookieValue, true);
// Dercrypt the cookie
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
// Create a new ticket with the desired data
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket
(
ticket.Version,
ticket.Name,
ticket.IssueDate,
DateTime.Now.AddYears(1),
true,
user.Authentication
);
// Update the cookies value
cookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Set(cookie);
accountRepo.Login(user);
With the Forms cookie created and with my Authentication data, which is basically the users hashed password, I then use the following logic to display the Login button or the username:
#{
var accountRepo = new AccountRepository();
var user = accountRepo.GetCurrentUser();
}
#if(user != null && user.LoggedIn) {
<div>#Html.ActionLink(Context.User.Identity.Name + " - Logout", "LogOff", "Account", null, new { #class = "logout_link" })</div>
}
else
{
#Html.ActionLink("Login", "Login", "Account", new { returnUrl = Request.Url.AbsoluteUri }, new { #class = "login_link" })
}
And that "GetCurrentUser()" method is:
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
return db.Users.SingleOrDefault(u => u.Authentications.Equals(ticket.UserData, StringComparison.CurrentCultureIgnoreCase));
}
return null;
Am I missing something here? I believe that with this code It should matter if the session restarts, my users should stay logged in.
Thanks in advance.
It's just as Mystere Man said. The cookie name was getting re-generated every time the session rebooted, so the app was looking for the cookie with a different name than what it had before.
For the peace of mind of all of you that helped me, and for the developer that will support this app in the future, I refactored it so its not that "evil" anymore :P