Symfony4. Force change password. Redirect to change-password site - security

I build login logic myself and i don't want use FosUserBundle.
I check Entity User date of last change password and if is more than 30 days i want that user will not fully authenticated only redirect to sites that they will must change password.
I try to use SecurityListener and onSecurityInteractiveLogin method.
Checking date works, but user is authenticated and redirect to sites what i configured in case after correct login (not to route change-password).
class SecurityListener
{
private $tokenStorage;
private $em;
private $session;
private $urlGenerator;
public function __construct(TokenStorage $tokenStorage, EntityManager $doctrine, Session $session, UrlGeneratorInterface $urlGenerator)
{
$this->tokenStorage = $tokenStorage;
$this->em = $doctrine;
$this->session = $session;
$this->urlGenerator = $urlGenerator;
}
/**
* #param InteractiveLoginEvent $event
* #return RedirectResponse
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$lastPasswordAt = $this->tokenStorage->getToken()->getUser()->getlastPasswordAt();
$data = new \DateTime("now");
$data->modify('-30 days');
if ($lastPasswordAt < $data) {
$this->session->invalidate();
return $this->urlGenerator->generate('app_change_password');
}
}
}
Please give me some hints how to handle this issue.

Related

Jhipster Microservices - Correct way to get UserId in Microservices?

I am using JHipster's Gateway with JWT and I have a microservice.
When the rest call is forwarded from the gateway to the microservice, in the microservice business class, I want to get the user id of the authenticated user.
The reason for this is I want to save it in the DB with the entity so that one user's data can be completely separate from other user's data (and a user cannot update another user's data...etc..).
While I can get the logged in user name, I don't have the user id.
What is the correct approach to resolving this issue:
call the gateway from the microservice ?
(this doesn't make too much sense to me as the gateway is calling the service and I'll want to know this info for most services).
update the TokenProvider in the gateway to include a user Id ? (not certain how to do this). Is this the correct approach ?
any other suggestions ?
Thanks,
Fergal.
Note: I see other similar questions. This is not a duplicate question. Do not mark this a duplicate unless absolutely certain. Note - I am using JWT
To solve this, I added the user id in the token from the gateway to each microservice.
Here is how I solved this in the JHipster generated code:
In Gateway, add UserService to UserJWTController, and get the user id, and
use it when you are creating a token.
public ResponseEntity<JWTToken> authorize(#Valid #RequestBody LoginVM loginVM) {
...
...
Optional<User> user = userService.getUserWithAuthoritiesByLogin(loginVM.getUsername());
Long userId = user.get().getId();
String jwt = tokenProvider.createToken(authentication, rememberMe, userId);
...
add the claim to the token:
claim(USER_ID_KEY, userId)
note, I added this to Token Provider:
private static final String USER_ID_KEY = "userId";
and then in my microservice's app, I did this:
created a new class:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class SamAuthenticationToken extends UsernamePasswordAuthenticationToken {
public Long getUserId() {
return userId;
}
private final Long userId;
public SamAuthenticationToken(Object principal, Object credentials, Long userId) {
super(principal, credentials);
this.userId = userId;
}
public SamAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, Long userId) {
super(principal, credentials, authorities);
this.userId = userId;
}
}
and then I changed TokenProvider.getAuthentication to add the following lines:
Long userId = null;
Object userIdObj = claims.get(USER_ID_KEY);
if (userIdObj != null) {
String userIdStr = userIdObj.toString();
userId = Long.parseLong(userIdStr);
log.debug("Claim--> {}", userId);
} else {
log.debug("No user id in token");
}
User principal = new User(claims.getSubject(), "", authorities);
return new SamAuthenticationToken(principal, token, authorities, userId);
and then I added a new method to SecurityUtils
public static Optional<Long> getUserId() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
if (authentication instanceof SamAuthenticationToken) {
SamAuthenticationToken samAuthenticationToken = (SamAuthenticationToken) authentication;
return samAuthenticationToken.getUserId();
}
return null;
});
}
and finally, I can now call this method from any Business class:
Optional<Long> userId = SecurityUtils.getUserId();
if (userId.isPresent()) {
log.info("User Id--->{}", userId.get());
} else {
log.info("No userId present.");
}
Any feedback welcome.

Logout doesn't logout in laravel socialite

I am unable to logout the google user in my application,when i logout it was redirecting login page saying it is successfully logged out but when i click login with google button it is redirecting to previous logged in user i.e., it is not logged out
my controller code:
class LoginController extends Controller {
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct() {
$this->middleware('guest')->except('logout','getLogout');
}
/**
* Redirect the user to the GitHub authentication page.
*
* #return \Illuminate\Http\Response
*/
public function redirectToProvider() {
return Socialite::driver('google')->redirect();
}
/**
* Obtain the user information from GitHub.
*
* #return \Illuminate\Http\Response
*/
public function handleProviderCallback() {
$user = Socialite::driver('google')->stateless()->user();
if($user) {
$authUser = $this->findOrCreateUser($user);
Auth::login($authUser, true);
}
return view ( 'home' )->withDetails ( $user )->withService ( 'google' );
// $user->token;
}
/**
* Return user if exists; create and return if doesn't
*
* #param $githubUser
* #return User
*/
private function findOrCreateUser($googleUser) {
if ($authUser = User::where('email', $googleUser->email)->first()) {
return $authUser;
}
return User::create([
'name' => $googleUser->name,
'email' => $googleUser->email,
]);
}}
I have followed the laravel documentation steps for this social login but i am unable to logout from my application..can anyone explain why this is going to happen??
Well, finally i found solution to my question.Actually, there is nothing to worry about it.Logout code is working pretty good.Google account ask for permission for first time login after getting grant access it won't ask for permission it directly continues to login.If we logout the google account then it will ask for permission otherwise it won't

What is the XsrfKey used for and should I set the XsrfId to something else?

In my MVC 5 web app I have this (in AccountController.cs):
// Used for XSRF protection when adding external sign ins
private const string XsrfKey = "XsrfId";
and
public string SocialAccountProvider { get; set; }
public string RedirectUri { get; set; }
public string UserId { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, SocialAccountProvider);
}
How exactly is it being used for protection?
Should I set the value of XsrfKey to something more random?
Take a look at ManageController methods LinkLogin and LinkLoginCallback:
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId());
}
//
// GET: /Manage/LinkLoginCallback
public async Task<ActionResult> LinkLoginCallback()
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
if (loginInfo == null)
{
return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}
These are the methods that handle linking of external accounts (i.e. Google, Facebook, etc.). The flow goes like this:
User clicks "Link Account" button, which calls a POST to LinkLogin method.
LinkLogin returns ChallengeResult object, with callback url set to LinkLoginCallback method.
ChallengeResult.ExecuteResult is called by MVC framework, calls IAuthenticationManager.Challenge, which causes a redirect to the specific external login provider (let's say: google).
User authenticates with google, then google redirects to callback url.
The callback is handled with LinkLoginCallback. Here, we want to prevent XSRF and verify that the call was initiated by a user, from a page served by our server (and not by some malicious site).
Normally, if it was a simple GET-POST sequence, you would add a hidden <input> field with an anti-forgery token and compare it with a corresponding cookie value (that's how Asp.Net Anti-Forgery Tokens work).
Here, the request comes from external auth provider (google in our example). So we need to give the anti-forgery token to google and google should include it in the callback request. That's exactly what state parameter in OAuth2 was designed for.
Back to our XsrfKey: everything you put in AuthenticationProperties.Dictionary will be serialized and included in the state parameter of OAuth2 request - and consequentially, OAuth2 callback. Now, GetExternalLoginInfoAsync(this IAuthenticationManager manager, string xsrfKey, string expectedValue) will look for the XsrfKey in the received state Dictionary and compare it to the expectedValue. It will return an ExternalLoginInfo only if the values are equal.
So, answering your original question: you can set XsrfKey to anything you want, as long as the same key is used when setting and reading it. It doesn't make much sense to set it to anything random - the state parameter is encrypted, so no one expect you will be able to read it anyway.
Just leave it as is:
As the name of the member states it is a key:
private const string XsrfKey = "XsrfId";
It is defined in this manner to avoid "magic numbers" and then is used a little down in the scaffold code:
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
The value of the dictionary item is then set to the UserId property in the above code by using the XsrfKey member as the key.
IOW the code is already setting the XSRF dictionary item to the value of the user ID in the snippet. If you change the XsrfKey members value to anything else you will cause problems down the line, since the expected key "XsrfId" will have no value set.
If by changing it to something more random you are implying to change the value and not they key of the dictionary, or in other words, not set it to the user id then please see the following for an explanation of the anti forgery token inner workings.
http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages

Automatically merge session user in Symfony2

I have written my own User-class extending Symfony's UserInterface and EquatableInterface which gets authenticated nice on logins and is correctly retrievable using $this->getUser(); in a controller.
Now, my question is, if it is possible to write something (dunno, a listener for example) that can automatically merge the user coming from the open session (so the one you retrieve with $user = $this->getUser();), so I dont have to call stuff like:
/**
* #Route("/url/to/route")
*/
public function myAction()
{
$user = $this->getUser();
$mergedUser = $this->getDoctrine()->getManager()->merge($user);
return array('user' => $mergedUser);
}
But instead:
/**
* #Route("/url/to/route")
*/
public function myAction()
{
/* User gets merged automatically! */
$mergedUser = $this->getUser();
return array('user' => $mergedUser);
}

setting bool value from a different class before navigating to that class

I'm trying to set a value to true after the user has been authenticated, so that they can use the page after authentication. When I set the value to true and redirect them to that same page that value is false again. I'm sure it has to do with different instances of the class but I dont know how to fix it.
This is the class that sets the value:
if (IsUserAuthorized())
{
Admin admin = new Admin
{
IsAuthorized = true
};
Response.Redirect("~/Admin.aspx");
}
else
{
LblErrorMessage.Text = "Please check your \"User Name\" or \"Password\" and try again.";
}
This is the class that needs to know the value:
public partial class Admin : System.Web.UI.Page
{
public bool IsAuthorized { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
if (IsAuthorized)
{//Do something} }
else
{
Response.Redirect("~/UserAuthentication.aspx");
}
}
classes do not persist between pages. What you need is either of the following two
Store the login status in cookies. That is how most sites do it. That is how e.g. on an email client you can navigate to various pages but still stay logged in.
Store the login status as session variables. Your login variable (true/false) resides in session.

Resources