MVC 5 OWIN - IsAuthenticated is false on external login (QQ Connect) - asp.net-mvc

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.

Related

How to implement remember me functionality properly? Asp.Net Core

I have been trying for 2 days to know how to build remember me functionality, but there is nothing clear.
First and foremost, I would like to make sure we agreed of the workflow of this properly as follows.
I need here to allow users to open their profile with no need to
signIn again for 1 month, as long as the user doesn't logOut.
I used cookie-based authentication to store some data that I can check every time when user profile opened to make sure that user is authenticated.
-- there is no problem with this step
I use in this step simple code to retrieve data again from the cookie.
-- and here is the problem comes. I can retrieve data from the cookie as long as I'm loggedIn, otherwise, when I stop and re-run the application and redirect to the user profile directly without logIn again I can't read the cookie data although it still exists!!!
Now let's take a look at code
Startup File Cookie Setting
public void ConfigureServices(IServiceCollection services){
.....
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.Name = "RememberMecookie"; // cookie name
options.LoginPath = "/Account/LogIn"; // view where the cookie will be issued for the first time
options.ExpireTimeSpan = TimeSpan.FromDays(30); // time for the cookei to last in the browser
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
options.EventsType = typeof(CookieAuthEvent);
});
.....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
}
.....
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
}
Login ViewModel
public class VMLogin
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Controller/Login
[HttpPost]
public async Task<IActionResult> LoginAsync(VMLogin CurrentUserLog, string returnUrl)
{
if (!string.IsNullOrEmpty(CurrentUserLog.UserName) && string.IsNullOrEmpty(CurrentUserLog.Password))
{
return RedirectToAction("Login");
}
if (ModelState.IsValid)
{
var SignInStatus = await signInManager.PasswordSignInAsync
(CurrentUserLog.UserName, CurrentUserLog.Password, CurrentUserLog.RememberMe, false);
AppUser _user = await userManager.FindByNameAsync(CurrentUserLog.UserName);
if (SignInStatus.Succeeded)
{
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) // to prevent login from outside link
{
return Redirect(returnUrl);
}
else
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, CurrentUserLog.UserName),
new Claim(ClaimTypes.Email, _user.Email),
new Claim(ClaimTypes.NameIdentifier, _user.Id.ToString())
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var props = new AuthenticationProperties{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
return RedirectToAction("UserProfile");
}
}
ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
}
return View(CurrentUserLog);
}
Here is all the problem. I get data from the cookie when I logIn for the first time with the first creation of the cookie as shown in
the code above. However, I can't get the same date from the same
cookie when I stop debugging and run the app again, and redirect to
UserProfile directly without logIn, although the cookie "RememberMecookie" still exists.
Controller/UserProfile
[Authorize]
public async Task<IActionResult> UserProfile()
{
// all lines of code below are working just with the first creation of the cookie with the first login. but if rerun the app again, they all return null if redirect here directly without logIn.
string userId = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
Claim v = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
AppUser _user = await userManager.GetUserAsync(HttpContext.User);
string cookieValueFromReq = Request.Cookies["RememberMecookie"];
// this is for normal login without remember me functionality
//AppUser user = await userManager.GetUserAsync(User);
return View(/*user*/);
}
Thanks For all guys who spent time checking out my question. I finally found the problem. This code is really great and it can be a good reference for remembering me functionality using cookie-based Authentication. And there is no problem with the code itself.
The problem was with my Startup file
It was like this
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.Cookie.Name = "RememberMeBlogAcademy";
options.LoginPath = "/Account/LogIn";
//options.LogoutPath = "/Home/Index";
//options.AccessDeniedPath = "AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
//options.Cookie.Expiration = TimeSpan.FromDays(5);
options.EventsType = typeof(CookieAuthEvent);
});
//services.AddScoped<CookieAuthEvent>();
services.AddControllersWithViews();
The problem was using MVC and AddControllersWithViews together. I didn't know that would make a problem.
However, It should be like this -- using AddControllersWithViews
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.Cookie.Name = "RememberMeBlogAcademy";
options.LoginPath = "/Account/LogIn";
//options.LogoutPath = "/Home/Index";
//options.AccessDeniedPath = "AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
//options.Cookie.Expiration = TimeSpan.FromDays(5);
options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();
services.AddControllersWithViews(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Moreover, You don't need to retrieve data from the cookie as shown in Controller/UserProfile above.
Also, when I made debugging to check out the code I tested logout to make sure I really retrieve users data from the cookie not from UserManager and It really works well.
Here is the additional code of logOut
[Authorize]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}

How to store bearer tokens when MVC and Web API are in different projects

Situation:
I have a Web API 2 project which acts as an Authorization server (/token endpoint) and a resource server. I am using the template that comes out of box with ASP.Net Web API minus any MVC reference.
The Start.Auth is configured as below:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings["Test_Facebook_AppId"],
AppSecret = ConfigurationManager.AppSettings["Test_Facebook_AppSecret"],
//SendAppSecretProof = true,
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(0);
}
}
};
facebookAuthenticationOptions.Scope.Add("email user_about_me user_location");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
}
The MVC 5 Client (different Project) uses the Web API app for authorization and data. Below is the code to retrieve the Bearer token in case of Username/Password store:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
model.ExternalProviders = await GetExternalLogins(returnUrl);
return View(model);
}
var client = Client.GetClient();
var response = await client.PostAsync("Token",
new StringContent(string.Format("grant_type=password&username={0}&password={1}", model.Email, model.Password), Encoding.UTF8));
if (response.IsSuccessStatusCode)
{
return RedirectToLocal(returnUrl);
}
return View();
}
Problem
I could retrieve the Bearer token and then add it to the Authorization Header for subsequent calls. I think that would be ok in case of an Angular App or a SPA. But I think there should be something in MVC that handles it for me, like automatically store it in a cookie and send the cookie on subsequent requests. I have searched around quite a lot and there are posts which hint towards this (Registering Web API 2 external logins from multiple API clients with OWIN Identity) but I haven't been able to figure out what to do after I get a token.
Do I need to add something in the MVC app Startup.Auth?
Ideally, I need the functionality which the AccountController in ASP.Net Template (MVC + Web API) gives out of box (Logins, Register, External logins, forget password etc etc...) but with the MVC and Web API in different projects.
Is there a template or a git repo which has this boiler plate code?
Thanks in advance!
Update
Incorporating #FrancisDucharme suggestions, below is the code for GrantResourceOwnerCredentials().
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
//Add a response cookie...
context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
But I can't still seem to get that Cookie or figure out what to do next.
Restating Questions:
What would be the correct way to authenticate, authorize and call Web API methods (Auth and Resource server) from an MVC client?
Is there boilerplate code or template for AccountController which does the basic plumbing (Login, register - internal/external, forgot password etc.)?
You could have your Startup class return a response cookie that the client will then return on all subsequent requests, here's an example. I would do it in GrantResourceOwnerCredentials.
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//your authentication logic here, if it fails, do this...
//context.SetError("invalid_grant", "The user name or password is incorrect.");
//return;
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
AuthenticationTicket ticket = new AuthenticationTicket(identity);
//Add a response cookie...
context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));
context.Validated(ticket);
}
The Startup class:
public partial class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public Startup()
{
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
//I use CORS in my projects....
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
WebApiConfig.Register(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, //I have this here for testing purpose, production should always only accept HTTPS encrypted traffic.
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
That assumes the client has cookies enabled, of course.
Then, modify your MVC headers to add the Authorization header to all requests as such.
In the ActionFilterAttribute, fetch your cookie value (Token) and add the header.
Instead of storing in session, I have added it to the the DefaultRequestHeaders as shown below so I don't need to add this in every call I make to Web API.
public async Task AuthenticateUser(string username, string password)
{
var data = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password)
});
using (HttpResponseMessage response = await APIClient.PostAsync("/Token", data))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<AuthenticatedUser>();
APIClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.Access_Token);
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}

Prevent multiple logins

I am trying to block multiple logins with the same user in my application. My idea is to update the security stamp when user signin and add that as a Claim, then in every single request comparing the stamp from the cookie with the one in the database. This is how I've implemented that:
public virtual async Task<ActionResult> Login([Bind(Include = "Email,Password,RememberMe")] LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
SignInStatus result =
await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, false);
switch (result)
{
case SignInStatus.Success:
var user = UserManager.FindByEmail(model.Email);
var id = user.Id;
UserManager.UpdateSecurityStamp(user.Id);
var securityStamp = UserManager.FindByEmail(model.Email).SecurityStamp;
UserManager.AddClaim(id, new Claim("SecurityStamp", securityStamp));
Then in authentication configuration I've added
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = ctx =>
{
var ret = Task.Run(() =>
{
Claim claim = ctx.Identity.FindFirst("SecurityStamp");
if (claim != null)
{
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = userManager.FindById(ctx.Identity.GetUserId());
// invalidate session, if SecurityStamp has changed
if (user != null && user.SecurityStamp != null && user.SecurityStamp != claim.Value)
{
ctx.RejectIdentity();
}
}
});
return ret;
}
}
});
As it shows I have tried to compare the claim from the cookie with the one in the database and reject the identity if they are not the same.
Now, each time the user signs in the security stamp gets updated but the value is different in user's cookie which I can't find out why? I am suspicious maybe it the new updated security stamp doesn't get stored in user's cookie?
The solution is somewhat more simple than you have started implementing. But the idea is the same: every time user logs in, change their security stamp. And this will invalidate all other login sessions. Thus will teach users not to share their password.
I have just created a new MVC5 application from standard VS2013 template and successfully managed to implement what you want to do.
Login method. You need to change the security stamp BEFORE you create auth cookie, as after the cookie is set, you can't easily update the values:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// check if username/password pair match.
var loggedinUser = await UserManager.FindAsync(model.Email, model.Password);
if (loggedinUser != null)
{
// change the security stamp only on correct username/password
await UserManager.UpdateSecurityStampAsync(loggedinUser.Id);
}
// do sign-in
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
This way every login will do an update on the user record with the new security stamp. Updating security stamp is only a matter of await UserManager.UpdateSecurityStampAsync(user.Id); - much simplier than you imagined.
Next step is to check for security stamp on every request. You already found the best hook-in point in Startup.Auth.cs but you again overcomplicated. The framework already does what you need to do, you need to tweak it slightly:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// other stuff
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
The time interval is set for zero - means the framework on every request will compare user's security stamp with the database. If stamp in the cookie does not match the stamp in the database, user's auth-cookie is thrown out, asking them to logout.
However, note that this will bear an extra request to your database on every HTTP request from a user. On a large user-base this can be expensive and you can somewhat increase the checking interval to a couple minutes - will give you less requests to your DB, but still will carry your message about not sharing the login details.
Full source in github
More information in a blog-post
In the past I've used IAuthorizationFilter and static logged-in user collection to achieve this:
public static class WebAppData
{
public static ConcurrentDictionary<string, AppUser> Users = new ConcurrentDictionary<string, AppUser>();
}
public class AuthorisationAttribute : FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext){
...
Handle claims authentication
...
AppUser id = WebAppData.Users.Where(u=>u.Key ==userName).Select(u=>u.Value).FirstOrDefault();
if (id == null){
id = new AppUser {...} ;
id.SessionId = filterContext.HttpContext.Session.SessionID;
WebAppData.Users.TryAdd(userName, id);
}
else
{
if (id.SessionId != filterContext.HttpContext.Session.SessionID)
{
FormsAuthentication.SignOut();
...
return appropriate error response depending is it ajax request or not
...
}
}
}
}
On logout:
WebAppData.Users.TryRemove(userName, out user)

Claims GetUserId is null

I hope you're all great, this time I'm here to ask an issue that I can't figure it out and it's about Identity, the thing is I want to get de User Id from the Claims principal or whereever it is, right now the only thing that I have is
var principal = ClaimsPrincipal.Current;
var id1 = principal.Claims.FirstOrDefault(c => c.ValueType == ClaimTypes.NameIdentifier);
but when I try to get the UserId and I go to the information inside the claims I can't find the value even if I saved it in the AuthorizationTicket at login.
I'm working with MVC template and Web api service My service is hosted in IIS and with it I manage the authentication with an accountcontroller via Token
this is my AuthenticationProperties
public static AuthenticationProperties CreateProperties(string userName, string userid)
{
IDictionary<string, string> data = new Dictionary<string, string> { { "userName", userName }, { "userId", userid } };
return new AuthenticationProperties(data);
}
and my GrantResourceOwnerCredentiales
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
//var userManager = DependencyResolver.Current.GetService<ApplicationUserManager>();
AppJobSeeker user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName, user.Id);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);...
And I have a method which creates a AuthenticationTicket and it receive the UserName and the UserId as well
private void CreateTicket(SignInResult result, SignInModel model, string returnUrl)
{
//Let's keep the user authenticated in the MVC webapp.
//By using the AccessToken, we can use User.Identity.Name in the MVC controllers to make API calls.
FormsAuthentication.SetAuthCookie(result.AccessToken, model.RememberMe);
//Create an AuthenticationTicket to generate a cookie used to authenticate against Web API.
//But before we can do that, we need a ClaimsIdentity that can be authenticated in Web API.
var claims = new[]
{
new Claim(ClaimTypes.Name, result.UserName), //Name is the default name claim type, and UserName is the one known also in Web API.
new Claim(ClaimTypes.NameIdentifier, result.UserId), //If you want to use User.Identity.GetUserId in Web API, you need a NameIdentifier claim.
};
//Generate a new ClaimsIdentity, using the DefaultAuthenticationTypes.ApplicationCookie authenticationType.
//This also matches what we've set up in Web API.
var authTicket = new AuthenticationTicket(new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie), new AuthenticationProperties
{
ExpiresUtc = result.Expires,
IsPersistent = model.RememberMe,
IssuedUtc = result.Issued,
RedirectUri = returnUrl
});
...
Everithing looks fine when I do the login but when a I go to another controller I can't retreive the UserId
Have you added the [Authorize] attribute to your controller?
[Authorize]
public class AuthorizeController : ApiController
{
public Task<IHttpActionResult> GetUserId()
{
return Ok(HttpContext.Current.User.Identity.GetUserId());
}
}

WsFederation and local user mixed authentication

I'm trying make my user login with Azure AD credentials (using OWIN WsFederation plugin) or using a local user account with microsoft asp.net identity in a MVC 5.1 Web App.
Login with local users work fine, login using a federated account works only once, and I need to restart my app to make it work again.
I suppose the problem is with the response from Microsoft login page not processed correctly
Infact, using two differente browsers (chrome+ie) in private mode and Fiddler, I can see that my cookie is set on first request but not on a subsequent request made from a different browser
First request
Second request
This is my ConfigureAuth
public void ConfigureAuth(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.SetDefaultSignInAsAuthenticationType("ExternalCookie");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
});
// these two lines of code are needed if you are using any of the external authentication middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ExternalCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
app.UseWsFederationAuthentication(new Microsoft.Owin.Security.WsFederation.WsFederationAuthenticationOptions()
{
MetadataAddress = "https://login.windows.net/XXXXXXX.onmicrosoft.com/federationmetadata/2007-06/federationmetadata.xml",
Wtrealm = "https://MYREALM",
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
});
}
This is part of the account controller
//
// POST: /Account/ExternalLogin
[HttpPost]
[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
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
var ctx = Request.GetOwinContext();
var result = ctx.Authentication.AuthenticateAsync("ExternalCookie").Result;
if (result != null) //null on request other than the first (!!!)
{
ctx.Authentication.SignOut("ExternalCookie");
var claims = result.Identity.Claims.ToList();
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, "External Account"));
var email = claims.Where(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name").SingleOrDefault().Value;
var ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(ci);
}
return RedirectToLocal(returnUrl);
}
In the ConfgureAuth set AuthenticationMode to Passive. It worked in my workflow which seems very similar to yours.
app.UseWsFederationAuthentication(new Microsoft.Owin.Security.WsFederation.WsFederationAuthenticationOptions()
{
MetadataAddress = "https://login.windows.net/XXXXXXX.onmicrosoft.com/federationmetadata/2007-06/federationmetadata.xml",
Wtrealm = "https://MYREALM",
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
AuthenticationMode = AuthenticationMode.Passive
});
http://msdn.microsoft.com/en-us/library/microsoft.owin.security.authenticationmode%28v=vs.113%29.aspx

Resources