Symfony 4: Authentication Token is lost from the Session after login_check redirect - authentication

Recently I have made some code changes to store sessions in Database using PdoSessionHandler. I am using Guard Authentication. checkCredentials is working fine is working fine, insert into "sessions" table is also working fine. But the Authentication token in the session is lost after /login_check redirect.
Authentication token is getting stored in the serialized format under "_security_secured_area" in the session and the session is also saved in the DB but after the redirect from /login_check to /login_redirect session is available with the same id but the auth token details are missing. Probably it is not able to populate auth details from the DB.
Here is my packages/security.yaml
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
anonymous: ~
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: _logout
target: _public_signin
logout_on_user_change: true
remember_me:
secret: '%kernel.secret%'
lifetime: 2592000 # 30 days in seconds
path: /
domain: ~
remember_me_parameter: _stay_signedin
# by default, the feature is enabled by checking a
# checkbox in the login form (see below), uncomment the
# following line to always enable it.
#always_remember_me: true
token_provider: token_service
Here is my gurardAuthenticator:
/**
* Override to change what happens after successful authentication.
*
* #param Request $request
* #param TokenInterface $token
* #param string $providerKey
*
* #return RedirectResponse
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
/** #var User $user */
$user = $token->getUser();
if ($user->getNewUser() === true) {
$url = '_onboard';
} elseif ($user->getResetPass() === true) {
$url = '_change_temp_password';
} else {
$url = '_login_redirect';
}
//$request->getSession()->save();
// MAS: TODO Add Audit probably in listener
return new RedirectResponse($this->urlGenerator->generate($url));
}
After AuthenticationSuccess it automatically redirects to loginReditrectAction() in SecurityController.php but here PostAuthenticationGuardToken is lost, AuthenticationEvent is returning AnonymousToken.
Another observation I found when I printed session in loginRedirectAction() in SecurityContrller.php is "_security_secured_area" in session data is missing.
#session: Session {#149 ▼
#storage: NativeSessionStorage {#148 ▶}
-flashName: "flashes"
-attributeName: "attributes"
-data: &2 array:2 [▼
"_sf2_attributes" => &1 array:4 [▼
"_csrf/https-kinetxx" => "rvR8Rr2UcDM_-y16ehk_jgYvMREJ8mTNouYCT16RtfY"
"_security.last_username" => "ktx_provider"
"userTimeZone" => "America/Chicago"
"practiceTimeZone" => "America/New_York"
]
"_symfony_flashes" => &3 []
]
My SecurityController.php
/**
* #Route("/login_redirect", name="_login_redirect")
*
* #param Request $request
*
* #return RedirectResponse
*/
public function loginRedirectAction(Request $request)
{
dump($request);
dump($this->get('security.authorization_checker'));
die;
}
Can someone help me resolving this?

I had this problem and the solution was to change my provider entity from
implements UserInterface, \Serializable
to
implements AdvancedUserInterface, \Serializable, EquatableInterface
and adding the needed methods: isEqualTo(UserInterface $user), isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled()

Related

Symfony 6.2 does not reach token handler on token authentication

Symfony 6.2 does not reach custom token handler while executing a request if token_extractors equals to header in the security.yml.
Here is the security.yml
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
access_token_provider:
entity:
class: App\Entity\AccessToken
property: secret
firewalls:
main:
lazy: true
provider: access_token_provider
stateless: true
pattern: ^/
access_token:
token_extractors: header
token_handler: App\Security\AccessTokenHandler
access_control:
- { path: ^/, roles: ROLE_ADMIN }
Here is the custom token handler, as you can see it shoud dump the token and die, and id does that if the token provided in query string.
namespace App\Security;
use App\Repository\AccessTokenRepository;
use Doctrine\ORM\NonUniqueResultException;
use SensitiveParameter;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
readonly class AccessTokenHandler implements AccessTokenHandlerInterface
{
public function __construct(private AccessTokenRepository $accessTokenRepository)
{
}
/**
* #throws NonUniqueResultException
*/
public function getUserBadgeFrom(#[SensitiveParameter] string $accessToken): UserBadge
{
var_dump($accessToken);
die;
$accessToken = $this->accessTokenRepository->getOneByToken($accessToken);
if (!$accessToken || !$accessToken->isValid()) {
throw new BadCredentialsException('Invalid credentials.');
}
return new UserBadge($accessToken->getId());
}
}
Authorization header generated by API client looks like this:
Authorization: Bearer 00000000-0000-0000-0000-000000000000
What's wrong in my implementation?
I tried to change token_extractors to query_string and provide token in GET parameters, and it does reach.
Query string looks like this:
localhost:8001/users?access_token=00000000-0000-0000-0000-000000000000
The problem was caused by Apache misconfiguration.
Apache didn't pass Authorization token to PHP due to default configuration and security reasons.
Adding CGIPassAuth On to a directory context solved this.
<Directory /var/www/html>
CGIPassAuth On
</Directory>
https://httpd.apache.org/docs/trunk/mod/core.html#cgipassauth

phpBB 3.1+ Authentication Plugin

I could really use some help with this.
BACKGROUND:
I've got phpBB 3.0 installed and have working external authentication from my own site's database. My working is an implementation of this excellent worked example: https://github.com/nzeyimana/PhpBBAuthDbExt/blob/master/auth_dbext.php
I now want to upgrade my Forum to 3.2 (current version).
PROBLEM:
Trying to follow the example in the documentation https://area51.phpbb.com/docs/dev/32x/extensions/tutorial_authentication.html#authentication-providers and also phpBB community/viewtopic.php?f=461&t=2272371
I've copied the class file from the example documentation, calling it db2.php and placed in "ext/acme/demo/auth/provider/"
I've also copied the service file from the example documentation, calling it services.yml and placed in "ext/acme/demo/config/"
Copies of both file contents at bottom below.
According to the documentation, I should then see db2 in the list of authentication methods in the Authentication part of Access Control Panel (ACP) - but nothing appears. I've flushed the forum cache, flushed my browsers cache etc, to no avail.
Am I missing something? Any help REALLY appreciated.
Kevin
This is the content of the db2.php file:
#
<?php
namespace acme\demo\auth\provider;
/**
* Database authentication provider for phpBB3
*
* This is for authentication via the integrated user table
*/
class db2 extends \phpbb\auth\provider\base
{
/** #var \phpbb\db\driver\driver_interface $db */
protected $db;
/**
* Database Authentication Constructor
*
* #param \phpbb\db\driver\driver_interface $db
*/
public function __construct(\phpbb\db\driver\driver_interface $db)
{
$this->db = $db;
}
/**
* {#inheritdoc}
*/
public function login($username, $password)
{
// Auth plugins get the password untrimmed.
// For compatibility we trim() here.
$password = trim($password);
// do not allow empty password
if (!$password)
{
return array(
'status' => LOGIN_ERROR_PASSWORD,
'error_msg' => 'NO_PASSWORD_SUPPLIED',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!$username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$username_clean = utf8_clean_string($username);
$sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
// Successful login... set user_login_attempts to zero...
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}
}
#
This is the content of the services.yml file:
#
services:
auth.provider.db2:
class: acme\demo\auth\provider\db2
arguments:
- '#dbal.conn'
tags:
- { name: auth.provider }
#
Unfortunately, the documentation is missing an important part - every extension should have its composer.json file in order to identify the extension - link.
You can refer to OneAll phpBB extension to see its structure and code. Use it as an example.
Once you have your composer.json, you should see you extension in the extension management list. Then enable your extension and you should see it in the Authentication section in your Access Control Panel (ACP)
I hope this helps.

How to set cookie before test in Laravel?

I need to test a specific behaviour based on the presence of a cookie, how do I set a cookie before sending the request (or visiting the page) ? For the moment the following fails, it behaves likes nothing was set.
$this->actingAs($user)
->withSession(['user' => $user, 'profile' => $profile]) ;
#setcookie( 'locale_lc', "fr", time() + 60 * 60 * 24 * 900, '/', "domain.com", true, true) ;
$this->visit('/profile') ;
Or
$cookie = ['locale_lc' => Crypt::encrypt('fr')] ;
$this->actingAs($user)
->withSession(['user' => $user, 'profile' => $profile])
->makeRequest('GET', '/profile', [], $cookie) ;
The problem was in the setting and reading of cookies. Neither #setcookie nor $_COOKIE will work from a testing context. The method makeRequest with a cookie array is the right one.
However !
The reading script (controller, middleware) must be using Laravel's $request->cookie() method and not directly try to access it with $_COOKIE. In my case the cookie needs to be read by another app on our domain so I also had to disable the encryption for that specific cookie, which can be done in EncryptCookies.php
EncryptCookies
<?php
protected $except = [
'locale_lc'
];
Test
<?php
$cookie = ['locale_lc' => 'fr'] ;
$this->actingAs($user)
->withSession(['user' => $user, 'profile' => $profile])
->makeRequest('GET', '/profile', [], $cookie) ;
Middleware
<?php
public function handle($request, Closure $next){
if($request->cookie('locale_lc')){...}
}

Zend 2 and auth configuration routing

I'm working curently on a Zend2 project where there is an authentifaction system for the whole website, it was fine until we had to develop a module which is an public web service.
I would like to know if it's possible to allow users to access to a specific module/routing of Zend 2 ?
The Zend\Authentication\Adapter\Http provides an easy way for Apache like authentication in Zend Framework 2 applications.
It comes with two implementations Basic and Digest HTTP Authentication, which can be combined with two sub components - the class itself or a FileResolver. We are going to use the FileResolver to read the stored credentials and compare them to the submitted values.
First thing first. There are few important things to know.
Create a folder with name auth in MODULE_NAME/config/. Inside that folder create two files basic.txt and digest.txt. The file formats are smillar to Apache .htpasswd files.
Basic - <username>:<realm>:<credentials>, here credentials should be written in clear text, e.g.: basic:authentication:plaintextpassword.
Digest - <username>:<realm>:<credentials>, where <credentials> is the md5 hash of all 3 parts, e.g.: digest:authentication:dc45122ef294d83e84a8b5a3a6c5356b
In the same module, where we have just created our auth folder, open module.config.php file and place this code.
The code tells us which authentication schemes we accept, the realm (must be the same as the realm in the basic/digest.txt files, digest_domains (only when we use digest authentication) is the URL(s) where we want to apply the same valid information, nonce_timeout sets the number of seconds for which the nonce is valid.
/**
* Used for basic authentication
*/
'authentication_basic' => [
'adapter' => [
'config' => [
'accept_schemes' => 'basic',
'realm' => 'authentication',
'nonce_timeout' => 3600,
],
'basic' => __DIR__.'/auth/basic.txt',
],
],
/**
* Used for digest authentication
*/
'authentication_digest' => [
'adapter' => [
'config' => [
'accept_schemes' => 'digest',
'realm' => 'authentication',
'digest_domains' => '/learn-zf2-authentication/digest',
'nonce_timeout' => 3600,
],
'digest' => __DIR__.'/auth/digest.txt',
],
]
LearnZF2Authentication\Factory\BasicAuthenticationAdapterFactory
$config = $serviceLocator->get('Config');
$authConfig = $config['authentication_basic']['adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$basic = new FileResolver();
$basic->setFile($authConfig['basic']);
$authAdapter->setBasicResolver($basic);
return $authAdapter;
LearnZF2Authentication\Factory\DigestAuthenticationAdapterFactory
$config = $serviceLocator->get('Config');
$authConfig = $config['authentication_digest']['adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$digest = new FileResolver();
$digest->setFile($authConfig['digest']);
$authAdapter->setDigestResolver($digest);
return $authAdapter;
These are the codes we use to pass the authentication information
Module.php
/**
* #var MvcEvent $e
*/
$request = $e->getRequest();
$response = $e->getResponse();
$view = $e->getApplication()->getMvcEvent()->getViewModel();
$sm = $e->getApplication()->getServiceManager();
$authAdapter = $sm->get('LearnZF2Authentication\BasicAuthenticationAdapter');
/**
* Not HTTP? Stop!
*/
if (!($request instanceof Http\Request && $response instanceof Http\Response)) {
return;
}
/**
* Call the factory class and try to authenticate
*/
if ($e->getRouteMatch()->getParam('action') == 'digest') {
$authAdapter = $sm->get('LearnZF2Authentication\DigestAuthenticationAdapter');
}
$authAdapter->setRequest($request);
$authAdapter->setResponse($response);
if($e->getRouteMatch()->getParam('action') == 'basic' || $e->getRouteMatch()->getParam('action') == 'digest') {
$result = $authAdapter->authenticate();
/**
* Pass the information to the view and see what we got
*/
if ($result->isValid()) {
return $view->identity = $result->getIdentity();
} else {
/**
* Create a log function or just use the one from LearnZF2.
* Also make sure to redirect to another page, 404 for example
*/
foreach ($result->getMessages() as $msg) {
return $view->authProblem = $msg;
}
}
}
This is the code we use to pass the authentication information
One last important thing to note is that you must include a special header called Authorization n your request, replace :
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
with
PHP compiled as CGI does not support apache_response_headers function, but we need this header in order to do basic HTTP authtentication when running with CGI or FastCGI.
RewriteRule ^(.*)$ %{ENV:BASE}index.php [E=HTTP_AUTHORIZATION:% {HTTP:Authorization},L,NC]
and add in top of public/index.php
if (isset($_SERVER["REDIRECT_HTTP_AUTHORIZATION"])) {
$_SERVER["HTTP_AUTHORIZATION"] = $_SERVER["REDIRECT_HTTP_AUTHORIZATION"];
}
Some things to note. The auth folder as well the authentication code from module.config.php is best to be placed in your main config folder, where the global|local.php files are and excluded from commits.

How can I detect 404 errors for page assets?

I've just gotten started with Behat and Mink. I'm using MinkExtension with Goutte and Selenium, and also DrupalExtension.
So far, so good. I can load a page, look for various elements, test links, etc.
But I don't see how to check for 404s on various assets - images, especially, but also css and js files.
Any tips or examples would be much appreciated.
When using Goutte web crawler you can do this:
$crawler = $client->request('GET', 'http://your-url.here');
$status_code = $client->getResponse()->getStatus();
if($status_code==404){
// Do something
}
You can try the following methods:
<?php
use Behat\Behat\Context\Context;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Client;
class FeatureContext extends WebTestCase implements Context {
/**
* #var Client
*/
private $client;
/**
* #When /^I send a "([^"]*)" request to "([^"]*)"$/
*
* #param $arg1
* #param $arg2
*/
public function iSendARequestTo($arg1, $arg2) {
$this->client = static::createClient();
$this->client->request($arg1, $arg2);
}
/**
* #Then /^the output should contain: "([^"]*)"$/
*
* #param $arg1
*/
public function theOutputShouldContain($arg1) {
$this->assertContains($arg1, $this->client->getResponse()->getContent());
}
/**
* #Then /^the status code should be "([^"]*)"$/
*
* #param $arg1
*/
public function theStatusCodeShouldBe($arg1) {
$this->assertEquals($arg1, $this->client->getResponse()->getStatusCode());
}
}
Source: FeatureContext.php at jmquarck/kate
Please check the following methods from this HelperContext.php (part of CWTest_Behat):
/**
* #Given get the HTTP response code :url
* Anonymous users ONLY.
*/
public function getHTTPResponseCode($url) {
$headers = get_headers($url, 1);
return substr($headers[0], 9, 3);
}
/**
* #Given I check the HTTP response code is :code for :url
*/
public function iCheckTheHttpResponseCodeIsFor($expected_response, $url) {
$path = $this->getMinkParameter('base_url') . $url;
$actual_response = $this->getHTTPResponseCode($path);
$this->verifyResponseForURL($actual_response, $expected_response, $url);
}
/**
* Compare the actual and expected status responses for a URL.
*/
function verifyResponseForURL($actual_response, $expected_response, $url) {
if (intval($actual_response) !== intval($expected_response)) {
throw new Exception("This '{$url}' asset returned a {$actual_response} response.");
}
}
/**
* #Given I should get the following HTTP status responses:
*/
public function iShouldGetTheFollowingHTTPStatusResponses(TableNode $table) {
foreach ($table->getRows() as $row) {
$this->getSession()->visit($row[0]);
$this->assertSession()->statusCodeEquals($row[1]);
}
}
Here are the example scenarios written in Behat which are using the above methods:
#roles #api #regression
Scenario: Verify Anonymous User access to /user/login
Given I am not logged in
Then I check the HTTP response code is 200 for '/user/login'
#roles #api #regression
Scenario: Verify Anonymous User access to /admin
Given I am not logged in
Then I check the HTTP response code is 403 for '/admin'
#roles #api #regression
Scenario: Verify Administrator access to /admin
Given I am logged in as a user with the admin role
And I am on "/admin"
Then the response status code should be 200
It's possible to use Restler, a micro framework which can help with RESTful API testing in Behat. It support behavior Driven API testing using Behat and Guzzle.
Check the following example:
Scenario: Saying
When I request "/examples/_001_helloworld/say"
Then the response status code should be 404
And the response is JSON
And the type is "array"