how to cancel authentication in AuthenticationSuccessHandler? - php

I made a child class that extends from DefaultAuthenticationSuccessHandler in order to add custom feature during the authentication process. I want to stop the authentication process if the criteria is not met. How do I do that?
public function onAuthenticationSuccess( Request $request, TokenInterface $token) {
if(some_condition_applies){
//if success, resume default flow
return parent::onAuthenticationSuccess( $request, $token);
}else{
//how to fail the authentication here?
}
}

AuthenticationSuccessHandler's main purpose is to do something AFTER the user has been authenticated. In other words it is too late to do anything about it.
You can create a custom authentication provider which handles all the authentication logic you require.
http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html#the-authentication-provider

Depending on your setup, the new SimpleFormAuthenticator functionality in Symfony 2.4 may help you out: How to Create a Custom Form Password Authenticator
This is probably only applicable if your authentication mechanism is username/password based (not OAuth or something).
Additionally, you could potentially just implement the AdvancedUserInterface, and then return "false" from a method like "isEnabled" to prevent login (Forbid Inactive Users). The only disadvantage is that you only have access to data in your User class. If you need something beyond that, the above method should work (but is a little bit more work).
Cheers!

Related

Symfony authentication event that fires only once?

I want to log logins.
But both InteractiveLogin and AuthenticationSuccess events fire on each request (because we use JWT to authenticate, which is passed as a header with every request), and the frontend does the redirection after a successful /api/login call, there is no way for me to know, whether the user just logged in.
How would you approach this?
If you are using a stateless firewall, as is the case when using JWT, the classic InteractiveLogin and AuthenticationSuccess are useless for this.
What you want to log is when the token is actually generated.
If you are using lexik/LexikJWTAuthenticationBundle, you could listen for a JWTCreatedEvent event to register that a user logged in.
class JwtCreatedSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'lexik_jwt_authentication.on_jwt_created' => 'onJwtCreated'
];
}
public function onJwtCreated(JWTCreatedEvent $event): void
{
$user = $event->getUser();
// record your "last logged-in" according depending on your application set-up
}
}
If you are not using this bundle, well, it depends how you are generating your tokens in the first place. But the basic idea would be the same: check for token generation and log that time.
A caveat to take into account: if you are using any kind of "token refresh" to maintain sessions without needing to log-in again, each time you refresh the session you would generate a new token... and register a new log-in time.

How to bypass usual Laravel authentication process to set a user as logged in

I am building a new Laravel application (v5.4) that will run alongside (installed in the same environment) an existing PHP application that has it's own authentication system. I want the users who have successfully logged in to the existing system to be automatically authenticated in the Laravel app so they can navigate seamlessly between the applications.
My Laravel app has read-only access (through a second db connection) to the existing system's database so it can safely check that the PHP Session matches the session cookie recorded in the database and I can even pull out the user object and hand it to Auth's login() method.
I want to know the best way to put Auth into an authorised state (not guest) and where is the best place to put such code?
Options I've thunked of so far:
Middleware: Check session and call the login() method on Auth from some application-wide middleware?
Extend Illuminate/Auth/SessionGuard.php and override the attempt() method? If so, how do I tell the other parts to use my extended SessionGuard? (I suspect this was not designed to be easily overridden)
Super hacky disgusting way of dynamically setting the user's password to a random hash and then calling Auth/LoginController#login() in the background with a faked request containing that hash as the password field. (I seriously hope this doesn't end up being the most popular answer)
Some other option (?)...
Thanks in advance for your help SO community!
The solution I ran with in the end was creating a middleware that contains this:
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (isSet($_SESSION['intranet_user_id']) && $_SESSION['intranet_user_id']) {
// Log them in manually
$intranet_user_id = $_SESSION['intranet_user_id'];
if (!Auth::guest() && Auth::user()->getId() !== $intranet_user_id ) {
Auth::logout();
}
if (Auth::guest()) {
Auth::login( User::find($intranet_user_id), true);
}
} else {
Auth::logout();
}

Lumen - Non token-based authenthication?

I trying to use lumen for the first time to create a restful backend service.
I'm used to working with laravel but with lumen I'm already stuck at the autentication. I can't find any tutorials on this.
I'm not even sure if my logic is secure for this. Bassically I receive a post request which contains an email and a password, then I want to check if the details are correct etc and authenticate the user.
I feel like I'm missing something, is this something that lumes comes with standard or will I need to rewrite the Auth service
It seems to be in the documentation you linked.
$this->app['auth']->viaRequest('api', function ($request) {
// Return User or null...
});
The Request class is passed in to this function. You would need to grab the email and password out of it $request->get('email') and request->get('password'), check to make sure they are valid.
I'm not sure of the best way to do this with Lumen or how much is available so to make it easy, you could just do something like the following...
$this->app['auth']->viaRequest('api', function ($request) {
$email = $request->get('email');
$password = $request->get('password');
$user = \DB::table('users')->where('email', $email)->first();
// Invalid Email
if ($user === null) {
return null;
}
// Check if password matches
if ( \Hash::check($user->password, $password) ) {
return $user;
}
// Invalid password
return null;
});
Keep in mind Lumen does not support session state you would need to pass in the email and password for every request. However, once it's setup, all you need to do in Lumen is use Auth::user() to grab the user.
You could also use jwt-auth which uses JSON Web Tokens which also makes it fairly easy and allows you to not pass emails and password around.
https://github.com/tymondesigns/jwt-auth
For anyone who encounters this problem. This is how i solved it:
In the auth serviceProvider (boot method) you check if there is a authorization header present. If there is one, it should include a apiToken, witch you can validate and continue with the normal flow.
If there is no Authorization header present, you can check the request variable for a email and password. Validate the login, and on success you save a new apiToken. I returned this token to the frontend, and made a feature that handles all ajax request, to include this token in the header. I also implemented a function that handles every response in my frontend application witch checks for a 401, when its there redirect to the login page.
With this aproach, you can use both auth methods, and Auth::user() is available through your application. Just make sure the login page is not handled with the Auth middleware!

Authorization and Policies for Guest users

Case: I'm building a forum using Laravel's Authorization as a backbone using policies. Examples of checks I run are stuff like #can('view', $forum), and #can('reply', $topic), Gate::allows('create_topic', $forum) etc. These checks basically checks if the users role has that permission for the specific forum, topic or post. That way I can give roles very specific permissions for each forum in my application.
The issue is that all of these checks go through the Gate class, specifically a method called raw() which in its first line does this:
if (! $user = $this->resolveUser()) {
return false;
}
This presents an issue when dealing with forums. Guests to my application should also be allowed to view my forum, however as you can see from the code above, Laravels Gate class automatically returns false if the user is not logged in.
I need to be able to trigger my policies even if there is no user. Say in my ForumPolicy#view method, I do if(User::guest() && $forum->hasGuestViewAccess()) { return true }
But as you can see, this method will never trigger.
Is there a way for me to still use Laravel's authorization feature with guest users?
I'm not aware of a super natural way to accomplish this, but if it's necessary, you could change the gate's user resolver, which is responsible for finding users (by default it reads from Auth::user()).
Since the resolver is protected and has no setters, you'll need to modify it on creation. The gate is instantiated in Laravel's AuthServiceProvider. You can extend this class and replace the reference to it in the app.providers config with your subclass.
It's going to be up to you what kind of guest object to return (as long as it's truthy), but I'd probably use something like an empty User model:
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
$user = $app['auth']->user();
if ($user) {
return $user;
}
return new \App\User;
});
});
}
You could go a step further and set a special property on it like $user->isGuest, or even define a special guest class or constant.
Alternatively you could adjust your process at the Auth level so that all logged-out sessions are wrapped in a call to Auth::setUser($guestUserObject).
I just released a package that allows permission logic to be applied to guest users. It slightly modifies Laravel's Authorization to return a Guest object instead of null when no user is resolved. Also every authorization check now makes it to the Gate instead of failing authorization instantly because there isn't an authenticated user.

Properly implementing resource authorization for a RESTful API

My team has an API we wrote in PHP using the Slim Framework. It's being consumed by a web app and a third party mobile app.
We use the standard OAuth 2 workflow to provide access to our resources. We can see if someone is a sending a valid access token along with the API request and that part of the flow makes sense.
The stumbling block we're running into is how to most efficiently authorize access to a resource depending on the permissions of the user associated with the access token.
For the sake of simplicity let's say that we just want to ensure that the owner of the resource in question matches the owner of the access token. To me that seems like something that a route middleware would handle, for every request, before processing the request make sure that the resource owner ID matches that of the access token.
The problem in my mind is that resource permissions aren't necessarily consistent across all routes, a route isn't necessarily going to have an ID in the same section of the URI, it might not have an ID in the URI at all, etc. etc.
My current solution has been to have an authorization utility class that takes in an email and checks it against the user that's currently "logged in" (token owner).
class Authorization() {
checkResourcePermissions($email) {
if (loggedInUser->email == $email) {
return true;
}
}
}
Obviously this is a simplification, but what this means is that since a route middleware won't have the context of a request until that request goes through I will need to call this authorization method inside of every API route, responding with an error if necessary, etc. Our API is fairly large, essentially boiling this down to a large amount of copy and paste which always makes me very nervous.
What am I missing?
I'd suggest going the way using a 'normal' MiddleWare:
First, let's assume you have a class 'user', where you implement your logic to access various types of resources or has specific permissions, given only the $app. It can access the request and get any information from a the HTTP request necessary to identify a single user.
class User{
private $userid=false;
public function __construct($app){
$this->app=$app;
$users_mail=$this->app->request->post('email');
//SELECT user_id FROM USERSTABLE WHERE email = $users_mail
//$this->userid=user_id
}
public function ensureHasAccess($resourceID,$redirect='/noPermission'){
if(!/*some DB Query which returns true if this user may access the given $resourceID*/)
$this->app->redirect($redirect)
}
public function ensureHasRole($role) {
//some other permission checking, like ensureHasAccess();
}
public function ensureIsUser(){
//do Something
}
}
Next, you'll need a MiddleWare which is run before the route is dispatched, and creates an User-Object:
class UserPermissionMiddleware extends \Slim\Middleware{
public function call(){
$this->app->request->user = new User($app);
$this->next->call();
}
}
Then, simply add this Middleware:
$app->add(new UserPermissionMiddleware());
Now, you can modify every route to check the access to a resource, while the resource id (of course) still needs to be supplied by hand, according to the route-specific code:
$app->get('/resource/:id',function($resourceId) use ($app){
$app->request->user->ensureHasAccess($resourceId);
//deliver resource
});
If the user is not allowed to access this resource, the whole process is interrupted (Note that redirect() is implemented using Exceptions!) and another route is executed. You'll still need some code in every single Route, but if there's no consistent way to determine the resource to be acccessed, there'll be no way around this.
Hope this helps you so far. If you have any Questions, feel free to ask!

Categories