Heyo!
I know it's a common problem people having problems with custom providers and web service authentication. I'm spending hours trying to figure out how to do that but I'm almost freaking out.
So, the thing is: I'm using the Symfony Firewalls with a custom UserProvider and a AbstractGuardAuthenticator as well. The problem is in the loadUserByUsername($username) function inside the UserProvider implementation.
For security reasons I can't retrieve the user password from Parse (my web service), and the loadUserByUsername($username) function asks for that. Even in the documentation about how to create a custom user provider using web services they are retrieving the user password from the database.
So what's the solution in that case? What can I do when I don't have access to the user password?
My current code is something like that:
$app['app.authenticator'] = function () {
return new Authenticator($app);
};
$app['security.firewalls'] = array(
'login' => array(
'pattern' => '^/login/$',
),
'secured' => array(
'pattern' => '^.*$',
'form' => array('login_path' => '/login/', 'check_path' => '/login/auth/'),
'logout' => array('logout_path' => '/logout/', 'invalidate_session' => true),
'guard' => array(
'authenticators' => array(
'app.authenticator'
),
),
'users' => function () use ($app) {
return new UserProvider($app);
},
)
);
The Authenticator.php is quite big code because extends the AbstractGuardAuthenticator class. But I'm basically using this one from Symfony docs. The only thing Is that I'm sending to the UserProvider class the username AND the password as well, because that way I can check if the user and password are right. Like this:
public function getUser($credentials, UserProviderInterface $userProvider) {
return $userProvider->loadUserByUsername($credentials);
}
And my UserProvider class is the default one, I'm just checking inside the loadUserByUsername function if the credentials comming from my Authenticator are right. Something like this:
public function loadUserByUsername($credentials) {
$encoder = new BCryptPasswordEncoder(13);
try {
$user = ParseUser::logIn($credentials['username'], $credentials['password']);
} catch (ParseException $error) {
throw new UsernameNotFoundException(sprintf('Invalid Credentials.'));
}
return new User($credentials['username'], $encoder->encodePassword($credentials['password'], ''), explode(',', 'ROLE_USER'), true, true, true, true);
}
The problem is: after the login (everything with the login is working fine), Silex calls the loadUserByUsername function in every page which needs to be secured, but just sending the username parameter.
So basically, I don't know what to do guys. I'm really trying to figure out how to get this thing working.
Thanks for your help!
I have a similar implementation and this issue is well known. In my user provider I have the methods loadUserByUsername($username) and refreshUser(UserInterface $user). Since I have the same issue like you, I don't check the user in loadUserByUsername but simple return a new Object with only the username in it to not disturb the flow. loadUserByUsername doesn't make sense for external APIs, so I simply jump over it. The method refreshUser is either beeing called on every request, this is usefull.
In your AbstractGuardAuthenticator you have the method createAuthenticatedToken, which returns an token. There you should have the full authentificated user:
abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
{
/**
* Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
* care about which authenticated token you're using.
*
* #param UserInterface $user
* #param string $providerKey
*
* #return PostAuthenticationGuardToken
*/
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
//do login stuff
//save password in user
return new PostAuthenticationGuardToken(
$user,
$providerKey,
$user->getRoles()
);
}
}
Then, I would't use loadUserByUsername but refreshUser instead. Both are called on every request:
/**
* Don't use
* #codeCoverageIgnore
* #param string $username
* #return User
*/
public function loadUserByUsername($username)
{
return new User($username, null, '', ['ROLE_USER'], '');
}
/**
* Refresh user on every subrequest after login
* #param UserInterface $user
* #return User
*/
public function refreshUser(UserInterface $user)
{
$password = $user->getPassword();
$username = $user->getUsername();
//login check
}
Related
I'm using external identity provider to authenticate users, created a SPA client (got client_id & client_secret), configured API with audience & scope, so once users authenticated they will get access_token (will be authorized) to access multiple custom micro-services (APIs).
When my custom API receives a request with a bearer Access Token (JWT) the first thing to do is to validate the token. In order to validate JWT I need to follow these steps:
Check that the JWT is well formed (Parse the JWT)
Check the signature. My external identity provider only supports RS256 via the JWKS (JSON Web Key Set) URL (https://{domain}/.well-known/jwks.json), so I can get my public key following this URL.
Validate the standard claims
Check the Application permissions (scopes)
There are a lot of packages/libraries (i.e. https://github.com/tymondesigns/jwt-auth) to create JWT tokens but I can't find any to validate it using those steps above. Could anyone please help to find suitable Laravel/PHP package/library or move me to the right direction in order to achieve my goals (especially point #2).
I did something similar in the past, I don't know if this may help but I'll give it a try. To use a public key, you should download it, put it somewhere on the disk (storage/jwt/public.pem for example) and then link it in the jwt config config/jwt.php with the ALGO (you can see supported algorithms here
'keys' => [
// ...
'public' => 'file://'.storage_path('jwt/public.pem'),
// ...
],
'algo' => 'RS256',
Then, you should have a custom Guard, let's call it JWTGuard:
<?php
namespace App\Guard;use App\Models\User;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
use Tymon\JWTAuth\JWT;class JWTGuard implements Guard
{
use GuardHelpers;
/**
* #var JWT $jwt
*/
protected JWT $jwt;
/**
* #var Request $request
*/
protected Request $request;
/**
* JWTGuard constructor.
* #param JWT $jwt
* #param Request $request
*/
public function __construct(JWT $jwt, Request $request) {
$this->jwt = $jwt;
$this->request = $request;
}
public function user() {
if (! is_null($this->user)) {
return $this->user;
}
if ($this->jwt->setRequest($this->request)->getToken() && $this->jwt->check()) {
$id = $this->jwt->payload()->get('sub');
$this->user = new User();
$this->user->id = $id;
// Set data from custom claims
return $this->user;
}
return null;
}
public function validate(array $credentials = []) { }
}
This should do all your logic of validation, I used a custom user implementation, the class signature was like:
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
class User extends Model implements AuthenticatableContract {
// custom implementation
}
Finally, you should register the guard in the AuthServiceProvider and in the auth config
public function boot()
{
$this->registerPolicies();
$this->app['auth']->extend(
'jwt-auth',
function ($app, $name, array $config) {
$guard = new JWTGuard(
$app['tymon.jwt'],
$app['request']
);
$app->refresh('request', $guard, 'setRequest');
return $guard;
}
);
}
then allow it in the config
<?php
return [
'defaults' => [
'guard' => 'jwt',
'passwords' => 'users',
],
'guards' => [
// ...
'jwt' => [
'driver' => 'jwt-auth',
'provider' => 'users'
],
],
// ...
];
You can then use it as a middleware like this:
Route::middleware('auth:jwt')->get('/user', function() {
return Auth::user();
}
Does this sound good to you?
In the end I've used the Auth0 SDK for Laravel - https://auth0.com/docs/quickstart/backend/laravel/01-authorization. Nice and clean solution.
So I use a Service Class (extends from TYPO3\CMS\Core\Authentication\AuthenticationService) to authenticate our Frontend Users using OAuth2. These Services are automatically instantiated and called via Typos own Middleware: FrontendUserAuthenticator.
In this class I used to save data from the authentication result to $GLOBALS['TSFE']->fe_user using setKey('ses', 'key', 'data'), which seems is not possible anymore since v10. How would I go about still doing this?
The documentation is sparse
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Deprecation-85878-EidUtilityAndVariousTSFEMethods.html
https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Context/Index.html
I've tried the following:
constructor injecting the TSFE using DI
class FrontendOAuthService extends AuthenticationService
{
public function __construct(TypoScriptFrontendController $TSFE) {
=> LogicException: TypoScriptFrontendController was tried to be injected before initial creation
changing the Middlewares order to have it instantiate before the Auth Middleware
(packages/extension_name/Configuration/RequestMiddlewares.php)
return [
'frontend' => [
'typo3/cms-frontend/tsfe' => [
'disabled' => true,
],
'vendor/extension_name/frontend-oauth' => [
'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
'before' => [
'typo3/cms-frontend/authentication',
],
'after' => [
'typo3/cms-frontend/eid',
'typo3/cms-frontend/page-argument-validator',
],
],
],
];
=> UnexpectedValueException: Your dependencies have cycles. That will not work out.
instantiating the TSFE myself
/** #var ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** #var DealerService $dealerService */
$lang = $site->getDefaultLanguage();
$siteLanguage = $objectManager->get(SiteLanguage::class, $lang->getLanguageId(), $lang->getLocale(), $lang->getBase(), []);
/** #var TypoScriptFrontendController $TSFE */
$TSFE = $objectManager->get(
TypoScriptFrontendController::class,
GeneralUtility::makeInstance(Context::class),
$site,
$siteLanguage,
GeneralUtility::_GP('no_cache'),
GeneralUtility::_GP('cHash')
);
=> the $TSFE->fe_user is an emptystring ("")
using the UserAspect
/** #var Context $context */
$context = GeneralUtility::makeInstance(Context::class);
$feUser = $context->getAspect('frontend.user');
$feUser->set...
=> Aspects are read-only
adding vars to the user data in the getUser method of the AuthenticationService
(packages/extension_name/Classes/Service/FrontendOAuthService.php)
public function getUser()
{
$user = allBusinessCodeHere();
$user['my_own_key'] = 'myData';
return $user;
=> is not propagated to the UserAspect(frontend.user) nor the $TSFE->fe_user
I'm out of ideas guys.
I had a similar problem when i wanted to use redirects with record links.
I ended up disabling the original redirect middleware and adding my own with a mocked version of tsfe.
The extension can be found here:
https://github.com/BenjaminBeck/bdm_middleware_redirect_with_tsfe
Late to the party, but I had the same issue and was able to solve it:
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.html states:
Storing session data from a Frontend User Session / Anonymous session
is now triggered within the Frontend User
(frontend-user-authenticator) Middleware, at a later point - once the
page was generated. Up until TYPO3 v9, this was part of the
RequestHandler logic right after content was put together. This was
due to legacy reasons of the previous hook execution order. Migration
Consider using a PSR-15 middleware instead of using a hook, or
explicitly call storeSessionData() within the PHP hook if necessary.
In my MyAuthenticationService extends AbstractAuthenticationService in method getUser() I set $_SESSION['myvendor/myextension/accessToken'] to the token received by the external oauth service. In my SaveSessionMiddleware I save this token to the FrontendUserAuthentication object using setKey() which by then is available:
EXT:myextension/Configuration/RequestMiddlewares.php
return [
'frontend' => [
'myvendor/myextension/save-session-middleware' => [
'target' => \MyVendor\MyExtension\Middleware\SaveSessionMiddleware::class,
'after' => [
'typo3/cms-frontend/authentication',
],
]
]
];
EXT:myextension/Classes/Middleware/SaveSessionMiddleware.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
class SaveSessionMiddleware implements MiddlewareInterface {
/**
* #param ServerRequestInterface $request
* #param RequestHandlerInterface $handler
* #return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (!empty($_SESSION['myvendor/myextension/accessToken'])) {
$this->getFrontendUserAuthentication()->setKey(
'ses',
'myvendor/myextension/accessToken',
$_SESSION['myvendor/myextension/accessToken']);
unset($_SESSION['myvendor/myextension/accessToken']);
}
return $handler->handle($request);
}
private function getFrontendUserAuthentication(): FrontendUserAuthentication {
return $GLOBALS['TSFE']->fe_user;
}
}
Please, I am new to symfony, I can't seem to make this work. I created a simple service that will check if the user status is active or deleted, and if they are, log them out from application. I have created the code that works, and then I wanted to be a badass and create a service that will do this. But nooo. So, I created a service and it prints out the result, so it is registered and it is working. The problem is I guess that I cannot approach the same variables as I did in Controllers to my service class. Here is the code:
<?php
namespace WebBundle\Roles;
class Roles
{
public function getApplicationId($loggedUser, $request)
{
// Get the current user role
/** #var $userRole array */
$userRole = $loggedUser->getRoles();
// Check if the user role is super admin or admin - client
/** #var $superAdmin bool */
$superAdmin = in_array('ROLE_SUPER_ADMIN', $userRole) ? true : false;
$admin = in_array('ROLE_ADMIN', $userRole) ? true : false;
if ($superAdmin) {
/** #var $application int */
$application = $request->get('application');
} elseif ($admin) {
/** #var $application int */
$application = $loggedUser->getAppClient()->getId();
}
return $application;
}
public function logoutInactiveAndDeletedUsers($loggedUser)
{
// Log out inactive or deleted users.
if ($loggedUser->getStatus()->getStatus() == 'inactive' || $loggedUser->getStatus()->getStatus() == 'deleted' ) {
//$this->get('security.context')->setToken(null);
//var_dump('test');exit;
return $this->container->render('WebBundle:Default:login.html.twig', array('last_username' => null, 'error' => null,));
}
}
}
So, this first service getApplicationId is working fine. But the other one is causing me real trouble. So the serivce is breaking when I call both:
$this->get('security.context')->setToken(null);
and
return $this->container->render('WebBundle:Default:login.html.twig', array('last_username' => null, 'error' => null,));
But If I put this code in Controller, it works perfectly. It destroys the token and redirect the user to the login page. Please, help me understand how to make it work as a service to.
Ok, I figure out how to call a view:
In services.yaml add templating as an argument
arguments: [#templating]
Then create a property and assign it to a constructor:
private $templating;
public function __construct($templating)
{
$this->templating = $templating;
}
And call it like $this:
$this->templating->render('WebBundle:Default:login.html.twig', array('last_username' => null, 'error' => null,));
Now I need to find a solution how to disable token for the user. If anybody know help me out dude (I am not stoner anymore).
I'm using https://github.com/tymondesigns/jwt-auth on my lumen application.
Here's my composer.json
"laravel/lumen-framework": "5.3.*",
"tymon/jwt-auth": "^1.0#dev",
I've read a of tutorials on how to install. Some of which are:
https://scotch.io/tutorials/role-based-authentication-in-laravel-with-jwt
https://laravelista.com/posts/json-web-token-authentication-for-lumen
I am able to make it work on my local and successfully return the token. But the problem is that instead of using eloquent on the provider that fetches data from database.sqlite, I want to use database as my driver.
With that, I have set
'providers' => [
'users' => [
'driver' => 'database',
'table' => 'user_table',
// 'driver' => 'eloquent',
// 'model' => App\User::class,
],
],
on my config/auth.php
Since it is now connection thru a database, it now uses the DatabaseUserProvider.php
I need to modify some codes though.
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, app('hash')->make($user->getAuthPassword()));
}
Notice that I added a app('hash')->make() when validating the password.
It then inject the retrieved user into the GenericUser object.
/**
* Get the generic user.
*
* #param mixed $user
* #return \Illuminate\Auth\GenericUser|null
*/
protected function getGenericUser($user)
{
if (! is_null($user)) {
return new GenericUser((array) $user);
}
}
Since it is on the GenericUser object, it gives an error of:
Argument 1 passed to Tymon\JWTAuth\JWT::fromUser() must be an instance of Tymon\JWTAuth\Contracts\JWTSubject, instance of Illuminate\Auth\GenericUser given
In order to fix this, I have to "hack" it by removing the JWTSubject injection on every method under the tymon\jwt-auth\src\JWT.php
Is there a better way to clean this up?
You can easily fix it by making your authenticating user implement JWTSubject, which is the most right thing to do.
class GenericUser implements JWTSubject {
[...]
}
But, since you're dealing with a Laravel native implementation, I'd suggest you to extend it and implement JWTSubject, instead of going everywhere in jwt-auth's code and remove the type-hints.
I'm have an authenticated section on my app, but the authentication is done via oauth to a 3rd party service. I get the 200 callback from the service, now I create/find my user and set him as logged in.
So my provider is:
providers:
users:
entity: { class: MainBundle:User, property: id }
My user implements the security's UserInterface, although I don't have the username, password, etc properties on my user entity. I assume the id is then the only identifier I can use then.
My token is set as follows:
$token = new UsernamePasswordToken($user, null, 'secured_area', $user->getRoles());
$this->securityContext->setToken($token);
I wish to do this without using JMS bundle for now; my serialization looks like this:
/**
* Serialize
* #return string|void
*/
public function serialize()
{
return serialize(array(
'id' => $this->getId(),
'display_name' => $this->getDisplayName(),
'email' => $this->getEmail(),
));
}
/**
* Unserialize
* #return mixed|void
*/
public function unserialize($data)
{
$data = unserialize($data);
$this->setId($data['id']);
$this->setDisplayName($data['display_name']);
$this->setEmail($data['email']);
return $this;
}
Using the above I get an infinite loop redirect.
At first I only serialized the id, but then all the other properties of the user isn't available.
Then I tried serializing the whole object ($this), but that gives me a xdebug nesting level error of 1000.
I'm a bit lost of how to make this authentication work with serialization
security.yml
providers:
users:
entity: { class: MainBundle:User, property: id }
login logic
$token = new UsernamePasswordToken($user, null, 'secured_area', $user->getRoles());
$this->securityContext->setToken($token);
I took out the serialization out of the user entity, also not implementing equatable interface. Also still do not have the interface properties, although this is needed:
public function getUsername()
{
return $this->getId();
}