I want to modify the default Web Application template to use Cookie Authentication instead of Identity. So here's what I did:
1/ Remove anything involves Identity
2/ Follow this guide https://docs.asp.net/en/latest/security/authentication/cookie.html
Problem
When I tried to access restricted resource (/Home/Secret), I am redirected to Login page => correct behavior.
I enter email/password and submit => cookie named .AspNet.MyCookieMiddlewareInstance created in client => correct behavior.
BUT then I got redirected to Account/AccessDenied instead of /Home/Secret. Where does /Account/AccessDenied come from?
I could not seem to figure it out. Can you help me out here?
Thanks
I had the same problem. After some research and tweaking it worked ...
Now I think the problem was the following. At first I had the Principal constructed as following
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(myclaims));
but actually it should have been like this
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(myclaims, "MyCookieMiddlewareInstance"));
Now this String "MyCookieMiddlewareInstance" apperently must be set.
Also this would be the full Configuration and Controller:
In Startup.cs
public void Configure(IApplicationBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "MyCookieMiddlewareInstance",
LoginPath = new PathString("/Auth/Login"),
AccessDeniedPath = new PathString("/Auth/Denied"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
And then in the AuthController.cs
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (model.Username == "test" && model.Password == "pass")
{
var myclaims = new List<Claim>(new Claim[] { new Claim("Id", "SOME USER ID FROM SOMEWHERE!!") });
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(myclaims, "MyCookieMiddlewareInstance"));
HttpContext.Authentication.SignInAsync("MyCookieMiddlewareInstance", claimsPrincipal).Wait();
return RedirectToAction("Index", "Home");
}
return View(new LoginModel());
}
Hope this helps even if its kinda late.
It happens because of the constructor you use to create new ClaimsIdentity instance. If you don't specify authentication type, IsAuthenticated property is set to false, and it causes the error you describe.
Here is a blog post on this topic
It is now possible to create a ClaimsIdentity that has claims, but having IsAuthenticated set to false.
...
To have IsAuthenticated set to true, you need to specify an authentication type in the ctor:
var id = new ClaimsIdentity(claims, “Custom”);
Related
I was reading article from here
This way we can add claim during login
var user = userManager.Find(userName, password);
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = persistCookie }, identity);
This way reading back the value stored in clam
var prinicpal = (ClaimsPrincipal)Thread.CurrentPrincipal;
var email = prinicpal.Claims.Where(c => c.Type == ClaimTypes.Email).Select(c => c.Value).SingleOrDefault();
Now I have few questions
How could I add my custom data to claim. Suppose user role names.
Suppose the things I want to add that is not available in ClaimTypes then how could I add my custom data to claim?
How to read back my custom data stored in claim?
My action is decorated with authorized attribute where role name is specified like below one:
..
public class HomeController : Controller
{
[Authorize(Roles = "Admin, HrAdmin")]
public ActionResult PayRoll()
{
return View();
}
}
Do I need to go for custom authentication to extract roles from claim to set in GenericPrincipal?
Last question: When we go for role based authorization then roles are stored in authorization cookie? Do I need to write code to store roles in authorization cookie or ASP.net engine does it for us?
Same way claims are store in authorization cookie generated by owin cookie?
If you are using Identity than identity have its own method which can handle roles and everything you just have to login with this line.
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
you will have to add role manager in Identity Config File
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
public ApplicationRoleManager(IRoleStore<IdentityRole, string> roleStore)
: base(roleStore)
{ }
public static ApplicationRoleManager Create(
IdentityFactoryOptions<ApplicationRoleManager> options,
IOwinContext context)
{
var manager = new ApplicationRoleManager(
new RoleStore<IdentityRole>(context.Get<ApplicationDbContext>()));
return manager;
}
}
and register in Startup.Auth.cs
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
and you won't have to give roles to the authentication manually. you just have to write
[Authorize(Roles = "Admin, HrAdmin")]
if you want to add that manually without identity given method than use this below
private void IdentityLogin(UserInfo UserInfo)
{
// Waleed: added the role in the claim
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, UserInfo.Email),
new Claim(ClaimTypes.Sid, UserInfo.UserID),
new Claim(ClaimTypes.Role, UserInfo.Roles)
}, DefaultAuthenticationTypes.ApplicationCookie);
var claimsPrincipal = new ClaimsPrincipal(identity);
// Set current principal
Thread.CurrentPrincipal = claimsPrincipal;
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignIn(identity);
}
Claims are of two types one are in your session and other are stored in db. Session Claims are above in IdentityLogin method and db claims can be written as
UserManager.AddClaim(userId,new Claim())
I'm cobbling together snippets of code from blogs and different places to try to get this to work. Normally, I'd refer to the reference documentation, but I can't find it here or anywhere else. It's just videos and demos for specific use cases that include user management or facebook or twitter.
I have a proprietary authentication service that I'm using. User accounts are not managed inside my application. So I need to be able to sign in a user that's completely constructed at run time.
Here's what I'm trying now in my MVC app.
using System.Security.Claims;
public class HomeController : Controller {
public ActionResult Scratch() {
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
var identity = new ClaimsIdentity(claims);
var authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.SignIn(identity);
return Content(
$"authentication manager type: {authenticationManager.GetType()} \n"
+ $"authenticated: {HttpContext.User.Identity.IsAuthenticated} \n"
+ $"user name: {HttpContext.User.Identity.Name} \n",
"text/plain");
}
}
The output is
authentication manager type: Microsoft.Owin.Security.AuthenticationManager
authenticated: False
user name:
Questions:
Why does the output show that the user has not been authenticated? What more do I have to do to get this user authenticated?
Where is the documentation for this framework?
Update
Startup.cs
public partial class Startup {
public void Configuration(IAppBuilder app) {
ConfigureAuth(app);
ConfigureAnalyticContext(app);
}
}
Startup.Auth.cs:
(there is actually much more, but all the rest has been commented out, in search of finding a minimal configuration that works)
public partial class Startup {
public void ConfigureAuth(IAppBuilder app) {
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
AnalyticContext.Auth.cs
(this is my Entity Framework context, I doubt it's related to this problem)
public partial class Startup {
public void ConfigureAnalyticContext(IAppBuilder app) {
app.CreatePerOwinContext(() => CentoAnalyticsContext.Create());
}
}
Well, it seems that you are not using ASP.NET Identity. ASP.NET Identity is new membership system of asp.net, which automatically creates database tables for storing users, encrypting password, etc.
What you are trying to do is to use the new authentication system provided by OWIN, which replaces the old FormsAuthentication style.
To make it work, you have to create the cookie authentication. Like this:
public static class AuthConfig
{
public const string DefaultAuthType = "DefaultAppCookie";
public const string LoginPath = "/System/SignIn";
public static void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthType,
LoginPath = new PathString(LoginPath)
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; //or whatever
}
}
In the login action:
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthConfig.DefaultAuthType);
IAuthenticationManager authManager = Request.GetOwinContext().Authentication;
authManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);
I think that should be enough to make it work in your app. A few days ago I answered a similar question MVC Authentication - Easiest Way, take a look, it might be helpful.
I recently have added Active Directory authentication, constructed ClaimsPrincipal myself and signed-in the same way you do.
And you are indeed missing .UseCookieAuthentication in your ConfigureAuth(IAppBuilder app)
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "MyAuthenticationName", // <-- this must match the AuthenticatioType name when you do sign-out
LoginPath = new PathString("/MyLoginPath"),
CookieName = "MyCookieName",
CookieHttpOnly = true,
});
}
And you don't need UseExternalSignInCookie.
Request.IsAuthenticated will be false with in the same request flow.
I think you still need to update the current security principal if you need to check IsAuthenticated for the request as authenticationManager.SignIn only validates the user against data store and sets the OWIN cookie which when sent back in subsequent request sets the security principal , usually a redirect takes care of this as in most cases there will be redirection in home page or something. If you still need to check with in the same request you can do something like below depending on your requirement
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
var identity = new ClaimsIdentity(claims,DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.User = principal;
Hope this helps.
I hope someone can help me out with this problem - it's driving me mad! :)
I'm trying to use external login through QQ Connect (OAuth 2.0) using tinysnake's QQ Connect provider: https://github.com/tinysnake/microsoft-owin-security-qq
Everything seems to be going great - I can sign in via my QQ account and I get posted back to my ExternalLoginCallBack-method with the appropriate claims etc.
I use these values to sign the user in through the IAuthenticationManager - all goes well. However - when I redirect the user to another page and checks if he's logged in - then I get a false value from the IsAuthenticated value... and I can't read any of the claims I set earlier.
It might be a simple fix - but I just can't see it right now :)
Some code:
AuthConfig:
public static void ConfigureAuthentication(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Normal cookie sign in
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
AuthenticationMode = AuthenticationMode.Active
});
// QQ CONNECT
app.UseQQConnectAuthentication(
appId: "XXXXXX",
appSecret: "XXXXXXXXXXXXXXXXX");
}
AccountController:
//
// POST: /Account/ExternalLogin
[System.Web.Mvc.HttpPost]
[System.Web.Mvc.AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
//
// GET: /Account/ExternalLoginCallback
[System.Web.Mvc.AllowAnonymous]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var ctx = Request.GetOwinContext();
var result = ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie).Result;
var claims = result.Identity.Claims.ToList();
var name = claims.First(i => i.Type == "urn:qqconnect:name");
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, "QQ"));
claims.Add(new Claim(ClaimTypes.Name, name.Value));
var ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ExternalCookie);
ctx.Authentication.SignIn(ci);
// DO OTHER STUFF HERE
return Redirect("~/");
}
All seems to be going well so far...
HomeController:
public ActionResult Index()
{
var model = new HomeViewModel();
var ctx = Request.GetOwinContext();
if (ctx.Authentication.User.Identity.IsAuthenticated) // <-- THIS RETURNS FALSE
{
var claimsIdentity = User.Identity as ClaimsIdentity;
model.Name = claimsIdentity.FindFirst(ClaimTypes.Name).Value;
model.IsAuthenticated = true;
}
return View(model);
}
When I check the ctx.Authentication.User.Identity.IsAuthenticated, I get a false value... and I can't retrieve any of the claims either.
Am I missing something?
Any help would be greatly appreciated :)
UPDATE
I got my code working by doing this in my AccountController:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var ctx = Request.GetOwinContext();
var result = ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie).Result;
if (result.Identity.IsAuthenticated)
{
// Signed in successfully
var claims = result.Identity.Claims.ToList();
var name = claims.First(i => i.Type == "urn:qqconnect:name");
//claims.Add(new Claim(ClaimTypes.AuthenticationMethod, "QQ"));
claims.Add(new Claim(ClaimTypes.Name, name.Value));
var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
}
return Redirect("~/");
}
But the way I see it - here I'm using the ApplicationCookie and NOT the ExternalCookie for signing in... or am I missing something entirely?
This solution works for me - but I'd like to know if this is the right way to be doing this?
From my understanding, what you are experiencing is expected. Extremely oversimplifying:
The app gets the external information and uses it to create an external cookie
the external cookie is sent to your app with the assumption that it is just a temporary cookie that will be used to look up any additional local information about the user and then converted to a local [application] cookie
See UseCookieAuthentication vs. UseExternalSignInCookie for a somewhat more thorough breakdown.
I need to define the Login page on an ASP.NET MVC 5.2 website using Owin so I tried:
public void Configure(IAppBuilder application) {
UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
// Testing routes > All returned null
var home = url.Action("home", "index");
var homeT4MVC = url.Action(MVC.Home.Index());
var login = url.Action("user", "login");
var loginT4MVC = url.Action(MVC.User.Login());
application.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider() {
OnApplyRedirect = context => context.Response.Redirect(
login.AddRouteValues(new { culture = Thread.CurrentThread.CurrentCulture.Name }))
)}
});
}
What is strange is that all the routes I tested are null ... But I have 116 routes in url.RouteCollection and I am able to use all my T4MVC routes.
What might be wrong here?
your routes will be generated later in a mvc application. here is owin's middleware, which is executed before the web application.
We use MVC 3. The default user management is not usable for us as our account info is stored in our own data-store and access goes via our own repository classes.
I'm trying to assign a principal add roles to the HttpContext.User and give out an authorization cookie.
Based on a code snipped I found I tried something like this:
if (UserIsOk(name, password))
{
HttpContext.User =
new GenericPrincipal(
new GenericIdentity(name, "Forms"),
new string[] { "Admin" }
);
FormsAuthentication.SetAuthCookie(name, false);
return Redirect(returnUrl);
}
When the next request is done, the user is authenticated, but he is not in the "Admin" role.
What am I missing?
I think you should implement FormsAuthenticationTicket.
More info here : http://msdn.microsoft.com/en-us/library/aa289844(v=vs.71).aspx
In Mvc it is quite similar.
I have a class called UserSession that is injected into LoginController and that I use in LogOn action :
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Index(LoginInput loginInput, string returnUrl)
{
if (ModelState.IsValid)
{
return (ActionResult)_userSession.LogIn(userToLog, loginInput.RememberMe, CheckForLocalUrl(returnUrl), "~/Home");
}
}
Here's my UserSession LogIn implementation (notice I put the "Admin" role hard coded for the example, but you could pass it as argument) :
public object LogIn(User user, bool isPersistent, string returnUrl, string redirectDefault)
{
var authTicket = new FormsAuthenticationTicket(1, user.Username, DateTime.Now, DateTime.Now.AddYears(1), isPersistent, "Admin", FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
if (authTicket.IsPersistent) authCookie.Expires = authTicket.Expiration;
HttpContext.Current.Response.Cookies.Add(authCookie);
if (!String.IsNullOrEmpty(returnUrl))
return new RedirectResult(HttpContext.Current.Server.UrlDecode(returnUrl));
return new RedirectResult(redirectDefault);
}
Then in the base controller I've overriden OnAuthorization method to get the cookie :
if (filterContext.HttpContext.Current.User != null)
{
if (filterContext.HttpContext.Current.User.Identity.IsAuthenticated)
{
if( filterContext.HttpContext.Current.User.Identity is FormsIdentity )
{
FormsIdentity id = filterContext.HttpContext.Current.User.Identity as FormsIdentity;
FormsAuthenticationTicket ticket = id.Ticket;
string roles = ticket.UserData;
filterContext.HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
I hope this helps. Let me know.
You sure, that roles are enabled, and there is such role?
If not, do following:
In Visual Studio:
Project -> ASP.NET Configuration
Then choose Security, enable roles. Create role "Admin".
Then try your approach