I didn't touch anything in the Laravel's registration, I just implemented basic login/register functionality and I created a route to activate users by email, like so. But I couldn't find how to login a user with remember me functionality after activated his account.
My route
Route::get('auth/activate/{token}', 'Auth\PasswordController#activate');
PasswordController
public function activate($token) {
//get token value.
// find the user that belongs to that token.
$activation = User::where("confirmation_code", $token)->get()->first();
// activate user account
$activation->confirmed = 1;
$activation->save();
Auth::loginUsingId($activation->id); // User is logged in now.
return view("frontend.feed.index");
}
The method loginUsingId looks like this:
Authenticatable loginUsingId(mixed $id, bool $remember = false)
So just add the optional parameter and set it to true.
Auth::loginUsingId($activation->id, true);
Related
In an asp.net core 3.1 web app with cookie-based authorization I have created a custom validator which executes on the cookie authorization's OnValidatePrincipal event. The validator does a few things, one of those is check in the backend if the user has been blocked. If the user has been blocked, The CookieValidatePrincipalContext.RejectPrincipal() method is executed and the user is signed out using the CookieValidatePrincipalContext.HttpContext.SignOutAsyn(...) method, as per the MS docs.
Here is the relevant code for the validator:
public static async Task ValidateAsync(CookieValidatePrincipalContext cookieValidatePrincipalContext)
{
var userPrincipal = cookieValidatePrincipalContext.Principal;
var userService = cookieValidatePrincipalContext.GetUserService();
var databaseUser = await userService.GetUserBySidAsync(userPrincipal.GetSidAsByteArray());
if (IsUserInvalidOrBlocked(databaseUser))
{
await RejectUser(cookieValidatePrincipalContext);
return;
}
else if (IsUserPrincipalOutdated(userPrincipal, databaseUser))
{
var updatedUserPrincipal = await CreateUpdatedUserPrincipal(userPrincipal, userService);
cookieValidatePrincipalContext.ReplacePrincipal(updatedUserPrincipal);
cookieValidatePrincipalContext.ShouldRenew = true;
}
}
private static bool IsUserInvalidOrBlocked(User user)
=> user is null || user.IsBlocked;
private static async Task RejectUser(CookieValidatePrincipalContext context)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
And here is the setup for the cookie-based authorization:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(co =>
{
co.LoginPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Login)}";
co.LogoutPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Logout)}";
co.ExpireTimeSpan = TimeSpan.FromDays(30);
co.Cookie.SameSite = SameSiteMode.Strict;
co.Cookie.Name = "GioBQADashboard";
co.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = UserPrincipalValidator.ValidateAsync
};
co.Validate();
});
This actually gets called and executed as expected and redirects the user to the login page when they navigate to a new page after having been blocked.
Most of the views have ajax calls to api methods that execute on a timer every 10 seconds. For those calls the credentials also get validated and the user gets signed out. However, after the user has been signed out, a popup asking for user credentials appears on the page:
If the user doesn't enter their credentials and navigate to another page, they get taken to the login page as expected.
If they do enter their credentials, they stay logged in, but their identity appears to be their windows identity...
What is going on here? What I would really want to achieve is for users to be taken to the login page for any request made after they have been signed out.
I have obviously misconfigured something, so that the cookie-based authorization doesn't work properly for ajax requests, but I cannot figure out what it is.
Or is it the Authorization attribute which does not work the way I'm expecting it to?
The code lines look good to me.
This login dialog seems to be the default one for Windows Authentication. Usually, it comes from the iisSettings within the launchSettings.json file. Within Visual Studio you'll find find the latter within your Project > Properties > launchSettings.json
There set the windowsAuthentication to false.
{
"iisSettings": {
"windowsAuthentication": false,
}
}
I have "mydomain\myusername" in the database with the role Administrator. I have ran a couple of test with different configurations. The comment with the "+" is given access, whereas, the "-" is required to log on. It seems as though a user is given access if it's authorized by itself. But when a role is added in, the role takes priority and it doesn't even look at the single user.
How do I get it to work where it takes the single user or multiple users into account when a role is specified? I am using a custom [DefaultAuthorize]:Asp.net MVC4: Authorize on both controller and action and OverrideAuthorize so that the controller and the action permission don't AND together, but it doesn't cause the behavior where the Users is ignored over the Role. That behavior seems to be the default behavior of the authentication.
Edited: I just tested it some more, and the solution from the SO above doesn't really work to create the OR in controller/actions, it still requires a logon if both are specified but user is only in the controller group. It works if user is in the action group.
Edited: The only thing I see for sure is defining Roles in the Actions works as expected. Adding Roles or users in the controller create a nonsensical behavior.
So there are two issues that boggles the mind. 1. Can't seem to get rid of the AND condition when roles are specified in the controller and actions. 2. the user is ignored over role.
I am using MVC5 with windows authentication and roles.
//-[Authorize(Roles = "Publisher,Editor", Users = "mydomain\\myusername")] //this should have worked since myusername is running the test
//-[Authorize(Users = "mydomain\\myusername",Roles = "Publisher,Editor")] //this should have worked also
//+[Authorize(Users = "mydomain\\myusername", Roles = "Administrator,Publisher,Editor")] //this works because of Administrator
//+[Authorize(Users="mydomain\\myusername")]
//+[Authorize(Roles = "Administrator")]
//+[Authorize(Roles = "Administrator", Users = "mydomain\\myusername")]
//+[Authorize(Roles = "Administrator,Editor", Users = "mydomain\\myusername")]
//+[Authorize(Roles = "Publisher,Administrator,Editor", Users = "mydomain\\myusername")]
[DefaultAuthorize(Roles = "Publisher,Editor")]
public class PersonEntitiesController : Controller
{
//default role and override role works as long as it is in a group
//-[Authorize(Roles = "Administrator")] doesn't work as it's AND with controller
//when a user is grouped with a Role, the role takes priority
//doesn't work as myusername is ignored and only looks at Publisher but user is not in gorup
//-[OverrideAuthorize(Users = "mydomain\\myusername",Roles="Publisher")]
//+[OverrideAuthorize(Roles="Administrator")]
//+[OverrideAuthorize(Users = "mydomain\\myusername")] //works as long as myusername is listed by itself
//+[OverrideAuthorize(Users = "mydomain\\myusername",Roles="Administrator")] //the group works as long as myusername is in that group
public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
This is the default behavior of the Authorize attribute. The attribute verify that all the following rules pass:
The user is not null, it has an identity and it's authenticated.
If user's names were included, that the identity name is included on those.
If roles were included, that any of the roles included is present on the identity roles.
Doing an inspection of the ASP.NET MVC's AuthorizeAttribute.IsAuthorized code confirms it:
protected virtual bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
IPrincipal user = actionContext.ControllerContext.RequestContext.Principal;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
{
return false;
}
return true;
}
So as you suspected this behavior works as an AND, not an OR. If you want to have a different behavior, I recommend that you create a custom Authorization attribute and put your own logic on it. Just inherit from AuthorizeAttribute and override the IsAuthorized method with your custom logic.
I need something like this:
if (Request.IsAuthenticated)
{
var user = await AccountManager.UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
if (regular account)
{
// do this
}
if (external provider account)
{
// do that
}
}
}
I found this in the debugger but I don't know if that's the right one to use for checking?
user.Logins.FirstOrDefault().LoginProvider
Which returns "Google"
For those looking, in ASP.NET Core 3.1 you can ask the user manager if the user has a password:
var user = await UserManager.GetUserAsync(User).ConfigureAwait(false);
var hasPassword = await UserManager.HasPasswordAsync(user).ConfigureAwait(false);
So all users can have passwords and external logins just to be clear. After a new user comes back from google/facebook, they are prompted to create a local user (which associates a login). If you want to test for the case where the user only has a password and no other logins, you can just see if the user has any associated logins.
UserManager.GetUserLogins().Count == 0
Keep in mind, users can unlink their accounts, and also link accounts via the manage page, so they will not necessarily stay in the same bucket over time...
I'd like to use WebSecurity+SimpleMembership, but implement the ability to (optionally) login users via a custom/alternative authentication method.
WebSecurity.Login only has one method signature, which requires both a username and a password. I'd like to skip the password check, e.g.:
if (MyCustomAuthenticationMethod.Authenticate(username, customData)) {
WebSecurity.Login(username); // Login without password check, method doesn't exist though
}
I assume custom-auth-methods are possible given OAuthWebSecurity exists, but I'm not sure how to go about implementing my own.
Well, you could simply go back to root of authentication and call directly
FormsAuthentication.SetAuthCookie
This will create cookie and authenticate your user.
See Asp.net Memebership Authorization without password
They didn't make it easy to login without a password. One method could be to make your own custom OAuth plug-in and simply call it with your own token like this:
OAuthWebSecurity.Login("google", "token", true);
You can find here how to create a custom OAuth provider:
http://www.codeguru.com/columns/experts/implementing-oauth-features-in-asp.net-mvc-4.htm
And you can browse the code here: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/Microsoft.Web.WebPages.OAuth/OAuthWebSecurity.cs
Here is a snippet from OAuthWebSecurity.cs file that shows the internals of how to user is authenticated without password:
internal static bool LoginCore(HttpContextBase context, string providerName, string providerUserId, bool createPersistentCookie)
{
var provider = GetOAuthClient(providerName);
var securityManager = new OpenAuthSecurityManager(context, provider, OAuthDataProvider);
return securityManager.Login(providerUserId, createPersistentCookie);
}
Perhaps someone out there already made this plugin.
In my grails app I have customized the post authorization workflow by writing a custom auth success handler (in resources.groovy) as shown below.
authenticationSuccessHandler (MyAuthSuccessHandler) {
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
superAdminUrl = "/admin/processSuperAdminLogin"
adminUrl = "/admin/processAdminLogin"
userUrl = "/admin/processUserLogin"
}
As you can from the last three lines in the closure above, depending on the Role granted to the logging in User I am redirecting her to separate actions within the AdminController where a custom UserSessionBean is created and stored in the session.
It works fine for a regular login case which in my app is like so:
User comes to the app via either http://localhost:8080/my-app/ OR http://localhost:8080/my-app/login/auth
She enters her valid login id and password and proceeds.
The app internally accesses MyAuthSuccessHandler which redirects to AdminController considering the Role granted to this User.
The UserSessionBean is created and stored it in the session
User is taken to the app home page
I have also written a custom MyUserDetailsService by extending GormUserDetailsService which is correctly accessed in the above flow.
PROBLEM SCENARIO:
Consider a user directly accessing a protected resource (in this case the controller is secured with #Secured annotation) within the app.
User clicks http://localhost:8080/my-app/inbox/index
App redirects her to http://localhost:8080/my-app/login/auth
User enters her valid login id and password
User is taken to http://localhost:8080/my-app/inbox/index
The MyAuthSuccessHandler is skipped entirely in this process and hence my UserSessionBean is not created leading to errors upon further use in places where the UserSessionBean is accessed.
QUESTIONS:
In the problem scenario, does the app skip the MyAuthSuccessHandler because there is a target URL for it to redirect to upon login?
Can we force the process to always pass through MyAuthSuccessHandler even with the target URL present?
If the answer to 2 is no, is there an alternative as to how and where the UserSessionBean can still be created?
You can implement a customized eventListener to handle the post-login process, without disrupting the original user requested url.
In config.groovy, insert a config item:
grails.plugins.springsecurity.useSecurityEventListener = true
In you resources.groovy, add a bean like this:
import com.yourapp.auth.LoginEventListener
beans = {
loginEventListener(LoginEventListener)
}
And create a eventListener in src/groovy like this:
package com.yourapp.auth
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
import org.springframework.web.context.request.RequestContextHolder as RCH
class LoginEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
//deal with successful login
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
User.withTransaction {
def user = User.findByUsername(event.authentication.principal.username)
def adminRole = Role.findByAuthority('ROLE_ADMIN')
def userRole = Role.findByAuthority('ROLE_USER')
def session = RCH.currentRequestAttributes().session //get httpSession
session.user = user
if(user.authorities.contains(adminRole)){
processAdminLogin()
}
else if(user.authorities.contains(userRole)){
processUserLogin()
}
}
}
private void processAdminLogin(){ //move admin/processAdminLogin here
.....
}
private void processUserLogin(){ //move admin/processUserLogin here
.....
}
}
Done.
1) Yes, because it is an "on-demand" log in.
2) Yes, you can set it to always use default. The spring security plugin has a setting for it "successHandler.alwaysUseDefault" change that to true it defaults to false.
Also if you need more details check out the spring docs look for the Setting a Default Post-Login Destination section.
3) If you want to still create the user session bean and then redirect to the original URL you have two options create the bean in an earlier filter or expose the needed data via a custom UserDetailsService. Personally I would go the route of a custom details service.