I am using ASP.NET Identity with MVC, and I set a sessionId (GuId string) on each logged in user for each one of his devices. The idea is that a user can remove device sessions, and then that device will not be logged in anymore (as it is done in dropbox, and google).
Currently, I set this sessionId as a claim in ASP.NET Identity, so it is passed in the authentication cookie.
For Authenrication I use ASP.NET Identity as the samples: app.UseCookieAuthentication(new CookieAuthenticationOptions{....
My questions:
Is setting my sessionId to the claims the right approach here?
Also, where in the whole authentication process can I validate the claim of that sessionId?
My current idea is to validate this sessionId against a database table for each request. Should I use Request.Sessions to store the sessionId instead, or any other idea here?
Thanks,
Since a user can have multiple valid sessions, you would either need to store these as claims or create your own table to store them. Since claims is already created by Identity, that will be easiest.
You'd validate this in the OnValidateIdentity method of CookieAuthenticationProvider in Startup.Auth.cs. Currently this calls the OnValidateIdentity method of the SecurityStampValidator so you need to write a wrapper method that first checks your session id and then calls the original security stamp validator. For example you could add these methods to the Startup class:
private Func<CookieValidateIdentityContext, System.Threading.Tasks.Task> _validate=SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager));
private async Task validate(CookieValidateIdentityContext context)
{
var usermanager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var claims = await usermanager.GetClaimsAsync(context.Identity.GetUserId());
//instead of setting to true, add your session validation logic here
bool sessionIsValid=true;
if (!sessionIsValid) {
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
}
await _validate(context);
}
where _validate is just the original method and validate is your new method that also checks session id. Then your app.UseCookieAuthentication code will reference the new validate method like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = validate
}
});
To make this work I think you'll need to check the claims from the database every time, but I believe the usermanager.GetClaimsAsync will ultimately do this.
Related
Using Owin + Oauth2 + Identity2.
I have a web Api with default basic authentication setup that i have modified.
my startup.cs partial class
public void ConfigureAuth(IAppBuilder app)
{
// 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);//TODO: prob wont need this
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),//TODO: prob wont need this
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true //TODO: set debug mode
};
// Token Generation
app.UseOAuthBearerTokens(OAuthOptions);
}
my startup.cs class partial at the root
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
my applicationOAuthProvider.cs
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//get user
var service = new CarrierApi.CarrierManagementClient();
var result = service.LoginAsync(context.UserName, context.Password);
var user = result.Result.Identity;
//TODO: log stuff here? i.e lastlogged etc?
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = user;
ClaimsIdentity cookiesIdentity = user;
AuthenticationProperties properties = CreateProperties(user.GetUserName());
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
As you can see i actually go and get the identity via a wcf call to our existing DB. when using postman, i get the /token url and obtain my bearer token, on the next request i pass it into the header and call my controller method.
[Authorize(Roles = "Templates_Access")]
public string Post([FromBody]string value)
{
return "woo";
}
This works great, if the user has the permission it wont allow access, if they do it does.
However if i go to our website that uses the same wcf and DB and change a users permission, if i send the same request on postman it still allows access even though ive removed that permission on the role the user is assigned too.
How do i make sure that permissions are "refreshed" or checked again on each request?
Every roles of a user logged in are stored in the bearer token at login time as claim, in the GrantResourceOwnerCredentials method. If a request have to be authorized, the role is searched on the list stored in the bearer token by the default implementation of AuthorizationFilter; so if you change the user's permissions, you need a new login.
This behavior respects the Stateless contraint of a Restfull architecture, as Fielding wrote in his dissertation, and also this is a good balance between performance and security
If you need a different behavior there is more than one possibility.
Refresh Token
You Can use Refresh Token, implementing the GrantRefreshToken method of applicationOAuthProvider class; you can retrieves the refreshed user's permissions and create a new access token; this is a good article to learn how.
Keep in mind:
more complexity on client
no real time effect; you have to wait the access Token expiring
If the Access Token has a short life, you have to update it often (even when the user permissions are not changed) otherwise a long life does not solve the problem
Check Permissions each request
You can implement a custom AuthorizationFilter and check in the database the permissions of the user, but it is a slow solution.
Cache and Login Session
You can generate a user session's key (like a guid) for each login in the GrantResourceOwnerCredentials method, and store it in the bearer token as a claim. You have to store it also in a cache system (like Redis), using two index: the user session's key and the userId. The official documentation of Redis explains how.
When the permessions of a user are changed, you can invalidate every sessions of that user in the cache system, searching by userId
You can implement a custom AuthorizationFilter and check for each request in cache if the session is valid, searching by user session's key.
Be careful: this will violate the stateless constraint and your architecture will not restfull
Here you can find the standard implementation of AuthorizaAttribute filter.
You can create your custom filter extending AuthorizeAttribute and overriding the IsAuthorized method.
Most likely there are other ways, but how often are changed the permissions of a user? In many systems, also in systems where the security is the first requirement, if the permissions's configuration of a user is changed during an active session, it is necessary a new login to active the new one.
Are you sure you needs to modify this standard behavior?
If you are, I suggest the solution with a cache system.
I have created two MVC applications that share the same authentication. In the applications I'm using different user roles that can be assigned to every user. When I log in as an administrator, everything works fine, I log in to the first application and the same cookie is used to log in to the second application, no login prompts involved.
When I log in as a user with a different role assigned to them, the login screen pops up again after logging in to the first application and it doesn't go away, even if I log in there also.
The applications are both on the same IIS server. The machine key is configured correctly in IIS server (obviously, since it works if I log in as a user with the administrator role assigned) and here is the code in Startup.Auth.cs for both applications:
1st Application:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "DefaultCookie",
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
2nd Application:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "DefaultCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("./Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
OnApplyRedirect = ApplyRedirect
},
});
private static void ApplyRedirect(CookieApplyRedirectContext context)
{
Uri absoluteUri;
if (Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out absoluteUri))
{
var path = PathString.FromUriComponent(absoluteUri);
Trace.WriteLine(path);
if (path == context.OwinContext.Request.PathBase + context.Options.LoginPath)
context.RedirectUri = "/Account/Login" +
new QueryString(
context.Options.ReturnUrlParameter,
context.Request.Uri.AbsoluteUri);
}
context.Response.Redirect(context.RedirectUri);
}
Does anyone know why this is happening and what I can do to fix it?
This is an authorization issue, not an authentication issue. If you can share the login at all, i.e. in the case of your admin user, then everything is fine on that front. However, the user's role must be authorized to access the controller/action or they will still be redirected to the login page, even though they are already authenticated. This is intended as an opportunity for them to re-authenticate with an account with the appropriate privileges, since the one they used apparently does not have access.
Long an short, you need to ensure that whatever controller(s)/action(s) you want the user to be able to access allow the role that is assigned to them.
I have simple project in ASP.NET 5 with one registered user. I have tried to get id of current logged user by extension method GetUserId() from using System.Security.Claims namespace.
Unfortunately this method returns me not existing id and i don't know why.
Here is my simple code:
var currentUserId = User.GetUserId();
Method result:
Database:
More than likely you're getting an old auth ticket that is still valid OR your getting an authentication ticket for another site your running locally. I would clear the browser cache and cookies and run it again.
Also, Try changing the name of the cookie, by default it is set to .AspNet.Cookies which can cause issues if you're running multiple apps locally. This caught me up a while back.
The cookie name can be changed like this
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(0),
regenerateIdentity: (manager, user) => manager.GenerateUserIdentityAsync(user))
},
CookieName = "myAuthCookie",
});
The MSDN documentation on it can be found here.
I'm using asp.net MVC and ASP.net Identity 2.0.
On my website Admin has option to ban user, and I would like when user is banned that he is automatically signed-out from website.
I know that I can sign-out current user by calling
AuthenticationManager.SignOut();
But is it possible to sign-out another user ? Or maybe shorter his session ? Or anything ?
I know I could make global filter on controllers prohibiting banned users from access but that filter would be ran against each user so I'm not quiet satisfied with that solution.
If you use the securitystampvalidator feature, when a user is banned just call: UpdateSecurityStamp(userId) to cause any existing login cookies to be invalid the next time they are checked.
More info about SecurityStamp?
You'll need to configure cookie invalidation in Auth.Config.cs:
public void ConfigureAuth(IAppBuilder app)
{
// important to register UserManager creation delegate. Won't work without it
app.CreatePerOwinContext(UserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<UserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(10),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
// other configurations
});
// other stuff
}
and then update security stamp as Hao Kung says when users are banned.
I've blogged about this recently
I've read this and while it explains how role changes will eventually propagate to the user cookie after some time interval, I still don't understand how I force an immediate change to user roles.
Do I really have to sign the user out when I change his roles as administrator? If so — how? If I use AuthenticationManager.SignOut(); then I sign off myself (admin), not the user, whose roles I want to change.
Currently I use await UserManager.UpdateSecurityStampAsync(user.Id); to generate a new security stamp, but it does not work. When I refresh a page in another browser while logged in as another user his claims (including security stamp) do not change.
If you want to enable immediate revocation of cookies, then every request must hit the database to validate the cookie. So the tradeoff between delay is with your database load. But you can always set the validationInterval to 0.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromSeconds(0),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});