Recently I have decided to add a "remember me" feature to my Laravel 4 app.
Appropriate method with syntaxis was found:
Auth::attempt(array $credentials = array(), $remember = false)
This was adopted for my needs like so:
Auth::attempt($userdata, Input::has('remember'))
Application kept the Auth session, and the user was authenticated even after browser was closed.
Although, I have found out that now Laravel always keeps a user authenticated, no matter what state "remember" checkmark is.
I have tried to do:
Auth::attempt($userdata, false)
and
Auth::attempt($userdata,)
User was still authenticated across the browser sessions!!!
Now, since Auth::attempt($userdata) not keeping the auth session, I felt that whenever there is an indications of the second argument in Auth::attempt method, Laravel auto assumes it as "true".
Can anyone clarify that?
EDIT:
To make it a super clear to everyone, I will list the steps to recreate this behaviour:
Logout of the app Auth::logout();
Login again Auth::attempt($userdata, false)
Close and open the browser
Go to the app url.
Application is loaded authenticated
This is my first question here, so please, be patient with me :)
EDIT : OP made clear he called Auth::logout() properly, so answer is edited to include the "Real" answer.
Set lifetime value in app/config/session/php to 0 to make cookie clear on browser close.
Previous answer
This is the login method in Illuminate\Auth\Guard (Which is facaded to Auth) class, which is eventually called by Auth::attempt().
source : http://laravel.com/api/source-class-Illuminate.Auth.Guard.html#263-291
public function login(UserInterface $user, $remember = false)
{
$id = $user->getAuthIdentifier();
$this->session->put($this->getName(), $id);
// If the user should be permanently "remembered" by the application we will
// queue a permanent cookie that contains the encrypted copy of the user
// identifier. We will then decrypt this later to retrieve the users.
if ($remember)
{
$this->queuedCookies[] = $this->createRecaller($id);
}
// If we have an event dispatcher instance set we will fire an event so that
// any listeners will hook into the authentication events and run actions
// based on the login and logout events fired from the guard instances.
if (isset($this->events))
{
$this->events->fire('auth.login', array($user, $remember));
}
$this->setUser($user);
}
It is clear that even though the cookie is set when $remember is set to true, the cookie itself is not cleared when $remember is set to false or other non-truthy value.
The cookie is cleared when you call Auth::logout() function.
Related
I am working on an Identity Server implementation that makes use of ASP.net Core 2.1 and IdentityServer4 libraries. In the context of OAuth2 protocol, the identity server is implemented in a way to return an AuthorizationCode as soon as the customer provides his/her login credentials through a server provided web-form. The code is returned by the server to a redirectURI that the customer has provided earlier when he first made the login request (see below shown sample login request).
1) EXAMPLE SCENARIO
Sample Login Request:
http://exampleABC.com:5002/connect/authorize?client_id=XYZ&scope=myscope&response_type=code&redirect_uri=http://exampleXYZ.com
Once above like request is issued in browser, the browser opens up a client login page where user is asked to type in his customerid and password. Then, an SMS token page is opened where the customer enters the SMS he has received at his cell phone. The customer then enters the SMS in the browser. Finally, the server redirects the customer's browser to the page at the redirectURI where the browser shows the AuthorizationCode (i.e. code) in the address bar as shown in the following:
https://exampleXYZ.com/?code=89c0cbe1a2cb27c7cd8025b6cc17f6c7cf9bc0d4583c5a63&scope=myscope
Here, the code "89c0cbe1a2cb27c7cd8025b6cc17f6c7cf9bc0d4583c5a63" can be now used to request an AccessToken from the identity server.
2) PROBLEM STATEMENT
If I re-issue the above indicated sample login request in the same client browser (e.g. chrome), then the browser redirects the user to the redirectURI immediately without re-asking the client login credentials. This is a problem because I have to open up a fresh login screen every time the login request is made considering that there can be customers who have different login credentials. Therefore, I have provided a logout endpoint in my IdentityServer implementation where I intend to clean out the entire client cache and then sign out the customer as shown in the following code block. Here, I delete the cookies first and then create a new one with same key and past expiration date in order that the cookie is removed from the client browser cache in addition to the server cache. My aim here is to bring the login web form up-front in the browser at all times with no caching in place if a logout request is issued in order that the login form is displayed every time a new comer customer arrives.
public async Task<IActionResult> Logout()
{
var vm = await BuildLoggedOutView();
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
try
{
if (HttpContext.Request != null && HttpContext.Request.Cookies != null && HttpContext.Request.Cookies.Keys != null && HttpContext.Request.Cookies.Keys.Count > 0)
{
foreach (var key in _accessor.HttpContext.Request.Cookies.Keys)
{
//!!!! Cookie Removal !!!!!!
//Here I delete the cookie first and then recreate it
//with an expiry date having the day before.
_accessor.HttpContext.Response.Cookies.Delete(key);
_accessor.HttpContext.Response.Cookies.Append(
key,
string.Empty,
new CookieOptions()
{
Expires = DateTime.Now.AddDays(-1)
});
}
}
//!!!! Explicit sign out!!!!!!
await _accessor.HttpContext.SignOutAsync();
}
catch (NotSupportedException ex) // this is for the external providers that don't have signout
{
}
catch (InvalidOperationException ex) // this is for Windows/Negotiate
{
}
return View("Logged out", vm);
}
3) QUESTION:
Although I delete the cookies and override them on server side, the client browser keeps returning into the page at redirect uri where a new authorization code is shown without enforcing the customer to login (which is undesired). So, my question here is what am I missing in the above code block? It looks neither cookie override with old expiry date nor the explicit call to SignoutAsync method does not help to sign out the customer completely. Is there some more explicit strategy you might suggest in order to clean out everything both on client and server side completely once logged out?
I've had the same issue with cookies not being deleted properly. In my case it was because I defined a specific path for the authentication cookies. Let's say my path was /path, in that case you have to specify the same path within your delete:
foreach (var cookie in Request.Cookies.Keys)
{
Response.Cookies.Delete(cookie, new CookieOptions()
{
Path = "/path",
// I also added these options, just to be sure it matched my existing cookies
Expires = DateTimeOffset.Now,
Secure = true,
SameSite = SameSiteMode.None,
HttpOnly = true
});
}
Also, I do not know if the .Append() is necessary. By using .Delete() it already sent a set-cookie header in my case.
I have a web application that employees log in to do stuff. What Im trying to achieve is: When a user logs in from a computer, if he is already logged in on another computer, that must be logged out. The web app is MVC Asp.Net Core 2.2 Code first. I have added a signInManager in startup and edited the PasswordSignInAsync method. I login the system from two different devices. When I click something on the screen from the first computer that I loggedin, it redirects to logout. It seems like working. But Im not sure if this is the right way of doing this. The code I added is: await UserManager.UpdateSecurityStampAsync(user); Inside PasswordSignInAsync method.
Inside the startup class ConfigureServices method I added
'services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddSignInManager<SignInManagerX>()'
Then in SignInManagerX class which is inherited from SignInManager I overrided the PasswordSignInAsync
public override async Task<SignInResult>
PasswordSignInAsync(ApplicationUser user, string password,
bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password,
lockoutOnFailure);
//Here is added
if (attempt.Succeeded)
{
await UserManager.UpdateSecurityStampAsync(user);
}
//Add end
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
Is this the right way ? or I should add a table to db for logins which holds the info if the user is already logged in on another Ip. Then Logging out that user from all computers if the last and current login attempt is true ?
Yes , the primary purpose of the SecurityStamp is to enable sign out everywhere.
The basic idea is that whenever something security related is changed on the user, like a password, it is a good idea to automatically invalidate any existing sign in cookies, so if your password/account was previously compromised, the attacker no longer has access.
Reference : https://stackoverflow.com/a/19505060/5751404
You can set validateInterval to TimeSpan.Zero for immediate logout .
I have an ember application that uses the Auth0 Ember Simple Auth addon to use the Ember-Simple-Auth functionality with Auth0's Lock.js. Recently I have been trying to implement single-sign-onfunctionality, such that if a user logs into a login portal application, their session will be preserved for other applications on the same domain, and they will not need to log in repeatedly. However my implementation of SSO is resulting in an infinite redirect loop between my login logic and Auth0's endpoint.
I have enabled SSO in the Auth0 application settings. My login is implemented in a few blocks.
My route.js contains a beforeModel() method which contains:
if (!get(session, 'isAuthenticated')){
// Forward to the login action
}
My login action:
login() {
var session = get(this, 'session');
session.authenticate('authenticator:myauthenticator', { authParams: { scope: 'openid' } });
}
This grabs the session object, and calls my custom authenticator. So far, this is basically just ember-simple-auth boilerplate, and complies with the examples supplied in the Auth0 Ember-Simple-Auth documentation.
Where I run into trouble is my custom authenticator. The base authenticator is here. You can see that it handles basic login logic easily, including showing the Auth0 lock when a user isn't authenticated. However it has no logic for handling the kind of SSO-session checking that I want to implement. So I implemented a custom authenticator as below, using examples provided by Auth0 for (basically) this exact scenario (you can see their examples [here], I'm using a slightly altered version)3:
authenticate(options) {
return new Ember.RSVP.Promise((res) => {
// the callback that will be executed upon authentication
var authCb = (err, profile, jwt, accessToken, state, refreshToken) => {
if (err) {
this.onAuthError(err);
} else {
var sessionData = { profile, jwt, accessToken, refreshToken };
this.afterAuth(sessionData).then(response => res(this._setupFutureEvents(response)));
}
};
var lock = this.get('lock');
// see if there's a SSO session available
lock.$auth0.getSSOData(function(err, data) {
if (!err && data.sso) {
// there is! redirect to Auth0 for SSO
options.authParams.callbackOnLocationHash = true;
lock.$auth0.signin(options.authParams, authCb);
} else {
// regular login
lock.show(options, authCb);
}
});
});
}
This behaves mostly as I would expect it to. When I log in with an existing session from another SSO-enabled app on the same domain, if (!err && data.sso) resolves to true, and lock.$auth0.signin(options.authParams, authCb) is called. However, this signin logic is not working as intended. Auth0.signin calls the Auth0.authorize method, which generates a target URL that looks something like:
https://mydomain.auth0.com/authorize?scope=openid&response_type=token&callbackOnLocationHash=true&sso=true&client_id=(MyClientIdHash)&redirect_uri=localhost%23access_token%3(MyAccessToken)%26id_token%3(MyIdToken1).(MyIdToken2).(MyIdToken3)token_type%3DBearer&auth0Client=(MyAuth0Client)
My application is then redirected to this URL for authorization. I get a 302 and am redirected back to the callback URL (my root page). Because there is a new page transition, if (!get(session, 'isAuthenticated')) is hit again. It returns false, and so the same logic repeats itself, looping indefinitely.
Does anyone have any insight on what I might be doing incorrectly here? The authorize endpoint seems to behave as if I were being authenticated, but then the authentication is never actually triggered. I've debugged through this code fairly extensively but seen no obvious red flags, and I've followed provided examples closely enough that I'm not sure what I would change. I'm not entirely sure where the failure to authenticate is happening such that get(session, 'isAuthenticated') is false.
The project i'm working on has a api behind a login.
I'm using behat with mink to login:
Scenario: Login
Given I am on "/login/"
And I should see "Login"
When I fill in "_username" with "test"
And I fill in "_password" with "test"
And I press "_submit"
Then I should be on "/"
This works..
However, the login session is not stored whenever i want to do the following using the WebApiContext:
Scenario: Getting the list of pages
When I send a GET request to "/api/pages.json"
Then print response
I'm using both scenarios in the same feature. My FeatureContext class looks something like this:
class FeatureContext extends MinkContext
{
public function __construct(array $parameters)
{
$context = new WebApiContext($parameters['base_url']);
$context->getBrowser()->getClient()->setCookieJar(new \Buzz\Util\CookieJar());
$this->useContext('web', $context);
}
}
I added the cookiejar idea from this issue without success.. When i print the response i just see the HTML page from the login screen..
Does anyone have any idea if i'm going at this totally the wrong way or am i somewhat in the right direction?
I am successfully using the same method. I don't think there's a standard way of doing this. As far as you understand the cookie basics you should be able to implement the solution.
In a common scenario, a client sends an authentication request to the server with some credentials, the servers validates it, starts an authenticated session and sends back a cookie with that session id. All following requests contain that id, so the server can recognise the callee. A specific header can be used instead of the cookie, or a database can be used instead of the session, but the principle is the same and you can (relatively) easily simulate it with Mink.
/**
* Start a test session, set the authenticated user and set the client's cookie.
*
* #Given /^I am signed in$/
*/
signIn()
{
session_start();
$_SESSION['user'] = 'jos';
$this->getSession()->getDriver()->setCookie(session_name(), session_id());
session_commit();
}
The above step definition (Behat 3) is the basics of it, you manually create the authenticated session and set to the client it's id. That must be also what the other example illustrates.
PHP's sessions can be problematic when you start doing more complex things and there are a couple of big underwater rocks with this solution. If you want to run assertions from both perspectives (the client and the server) you might often need to have your sessions synced. This can be done by updating the cookie before all Mink steps and reloading the session after.
/**
* #beforeStep
* #param BeforeStepScope $scope
*/
public function synchroniseClientSession(BeforeStepScope $scope)
{
// Setup session id and Xdebug cookies to synchronise / enable both.
$driver = $this->getSession()->getDriver();
// Cookie must be set for a particular domain.
if ($driver instanceof Selenium2Driver && $driver->getCurrentUrl() === 'data:,') {
$driver->visit($this->getMinkParameter('base_url'));
}
// Also enables the debugging support.
$driver->setCookie(session_name(), session_id());
$driver->setCookie('XDEBUG_SESSION', 'PHPSTORM');
}
/**
* #afterStep
* #param AfterStepScope $scope
*/
public function synchroniseServerSession(AfterStepScope $scope)
{
$driver = $this->getSession()->getDriver();
// Only browser kit driver, only initiated requests, only not repeating requests.
if (!$driver instanceof BrowserKitDriver) {
return;
} elseif (($request = $driver->getClient()->getRequest()) === null) {
return;
} elseif ($request === self::$request) {
return;
}
// Your logic for reloading the session.
self::$request = $request;
}
The biggest problem I had was the session reloading. This might be due to my framework of choice, which I doubt. The very first code snippet has session_commit(), which saves and closes the session. In theory in the following step definitions you must be able to session_id(/* session id from the cookie… */); and session_start();, but in practice that didn't work and no session data was actually loaded from the file, though the session did start. To solve this I created a custom session manager with reload() method using session save handler.
Second problem is where you cannot simply close the session without either writing it or destroying it (the support is added in PHP 5.6) on which relies the reloading itself. I reinvented the wheel with a flag for the session manager which tells it whether to write or just to close it.
:)
When a user comes to my site, i used to open a new window to authenticate the user and ask for permission through my app(call it as UseCase1). But when a user is already logged-in i was trying to fetch the logged-in user info through PHP SDK so that i need not open a new window and check for the same(You will be avoiding opening a new window that closes after a while since the user is already logged in). But the getUser() method doesn't return me anything. The same method works fine when i go through the UseCase1(open new window which closes automatically).
Below is the code i am trying to use:
$facebook = new Facebook(array(
'appId' => $appId,
'secret' => $appSecret,
'oauth' => true
));
$me = $facebook->getUser();
if ($me) {
try {
$user_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
//code to redirect to login url
// USECASE1
// use $facebook->getUser() one USECASE1 is over
// now the user info is generated properly
}
}
All the times the user is going through USECASE1(but with an empty window since user is logged in and already authenticated the app) even though he is already logged-in and already authenticated the app previously.
Any idea why this is happening
In your code, if the user is getting to 'USECASE1', then it shows that '$me' is being set, because it is getting to your try/catch.
Using $me = $facebook->getUser(); is not a test for whether a user has accepted permissions for your application yet.
I suspect that the reason for the 'FacebookApiException' Exception being thrown is because you haven't got your user permissions yet.
I'd suggest reading the authentication flow and the example PHP SDK authentication flow, see the following links:
http://developers.facebook.com/docs/reference/php/facebook-api/
http://developers.facebook.com/docs/authentication2/