ZF2 + How to change session timeout with Zend\Authentication - authentication

I was able to define my own authentication service with Zend\Authentication. Now I want to be able to change the session timeout limit.
I don't want to change the general session config but only for this Authentication manager.
As I want to reuse this adapter, I want to find a way to do it through the module.config.php configuration file.
For the moment, I am creating my service like that
public function getServiceConfig()
{
return array(
'factories' => array(
'cas_auth_service' => function ($sm) {
$authService = new \Zend\Authentication\AuthenticationService;
$authService->setAdapter($sm->get('\CasAuthen\Authentication\Adapter\Cas'));
return $authService;
},
),
);
}
I was thinking about creating my own Authentication Storage adapter and then injecting it but I don"t know if it is the right way to do it.
Does anymore know how to proceed ?
Thank you in advance and have a nice day.

Related

Can changes for session's idle timeout value, during application runtime, take effect immediately?

This may be an extremely unnecessary implementation, but please bear with me.
Startup.cs
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(Configuration.GetValue<double>("Custom:SessionTimeout"));
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
appsettings.json
{
...,
"Custom": {
"CookieTimeout": 15,
"LockTimeout": 15,
"SessionTimeout": 15
}
}
My intention is to allow the ability to modify the value for session timeout. As provided above, the Session's IdleTimeout in Startup.cs uses a value defined in the appsettings.json. For testing, I published the application to IIS, I restarted the server. The default value was set to 15 (minutes). It worked properly. However, afterwards, regardless of any value changed, it will still use the default value (15), unless the server was restarted.
My question is, is it possible to have the changes immediately take effect without performing a restart on the server?
First the IOptions<SessionOptions> is consumed once by the singleton middleware of SessionMiddleware. That options is singleton as well. So you just need to obtain that options and update the values.
However the point here is when to update it? As you want looks like it's when you save the configuration file. Asp.net core supports a feature to monitor the file changes. The change will be notified via an IChangeToken (which can be used only once for each obtained token). After handling the change, you need to obtain another change token to observe the change. However you don't have to do that logic manually because we have the static method ChangeToken.OnChange which can be conveniently to configure the change handler. It accepts a factory to get IChangeToken and a handler (to be called after some change occurs). The IConfiguration exposes a method called GetReloadToken to get an IChangeToken so we can use that.
Here's the code:
//inside Startup.ConfigureServices
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(Configuration.GetValue<double>("Custom:SessionTimeout"));
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
//update options on changes made to the configuration file
ChangeToken.OnChange(
() => Configuration.GetReloadToken(),
so => {
so.IdleTimeout = TimeSpan.FromMinutes(Configuration.GetValue<double>("Custom:SessionTimeout"));
}, options);
});

Why is the callback identifier not being invoked?

I'm trying to implement matching a Kerberos authentication with a local user database in CakePHP4. So I installed CakePHP 4 and the Authentication plugin 2.0. Since Kerberos auth is managed by our IIS WebServer, only thing I have to do is check if the authenticated user is known by my webapp.
The callback authentication should let me implement something like this, right ?
So I put this function in Application.php :
<?php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
// Define where users should be redirected to when they are not authenticated
$service->setConfig([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect',
]);
// Load the authenticators. Session should be first.
$service->loadAuthenticator('Authentication.Session');
$service->loadIdentifier('Authentication.Callback', [
'callback' => function($data) {
// do identifier logic
if (empty($_SERVER['REMOTE_USER'])) {
return new Result(
null,
Result::FAILURE_OTHER,
['message' => 'Unknown user.']
);
} else {
// On vérifie que l'utilisateur est autorisé à utiliser cette application
$users = TableRegistry::getTableLocator()->get('Users');
$remoteUserNoDomain = str_replace("DOMAIN\\", "", $_SERVER['REMOTE_USER']);
$result = $users->find()
->where(['username' => $remoteUserNoDomain]);
if ($result) {
return new Result($result, Result::SUCCESS);
}
return new Result(
null,
Result::FAILURE_OTHER,
['message' => 'Removed user.']
);
}
return null;
}
]);
return $service;
}
But so far, it doesn't seem to work, like it won't call the callback function at all. I tried to put some debug code, exits... Nothing works.
I would assume that you've also done all the other required configuring for authentication to work, ie loading the plugin, adding the authentication middleware, etc.!?
https://book.cakephp.org/authentication/2/en/index.html
That said, identifiers do not do any work on their own, they are being triggered by authenticators in case they actually require them. You only have the Session authenticator loaded, which in its default configuration doesn't make use of identifiers, but even if you configure it to use identifiers (by setting its identify option to true), it will only use them when there already is an identity in the session, then the identifier is being used to validate that identity.
https://github.com/cakephp/authentication/blob/2.3.0/src/Authenticator/SessionAuthenticator.php#L52
I'm not familiar with Kerberos authentication, but if it pre-populates $_SERVER['REMOTE_USER'] (btw. never access superglobals in CakePHP directly, it will only cause trouble down the road), then what you need is a custom authenticator. You could then re-use the password identifier for the ORM access part, as it allows finding something without checking the password (weirdly enough, given its name).
Quick and dirty example based on your snippet:
// src/Authenticator/KerberosAuthenticator.php
namespace App\Authenticator;
use Authentication\Authenticator\AbstractAuthenticator;
use Authentication\Authenticator\Result;
use Authentication\Authenticator\ResultInterface;
use Psr\Http\Message\ServerRequestInterface;
class KerberosAuthenticator extends AbstractAuthenticator
{
public function authenticate(ServerRequestInterface $request): ResultInterface
{
$server = $request->getServerParams();
if (empty($server['REMOTE_USER'])) {
return new Result(null, Result::FAILURE_CREDENTIALS_MISSING);
}
$remoteUserNoDomain = str_replace("DOMAIN\\", "", $server['REMOTE_USER']);
$user = $this->_identifier->identify(['username' => $remoteUserNoDomain]);
if (empty($user)) {
return new Result(
null,
Result::FAILURE_IDENTITY_NOT_FOUND,
$this->_identifier->getErrors()
);
}
return new Result($user, Result::SUCCESS);
}
}
Your service authenticator/identifier setup would then look like this:
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Kerberos');
$service->loadIdentifier('Authentication.Password');
Nore sure if you'd then really want to use the session authenticator like that though, ie whether you only want to identify the remote user once per session.

Using CakePHP Form and Basic Authentication together

I've created a simple test site using CakePHP 3.8 and Authentication 1.0 to try it out. I'd like to use both Form and Basic authentication since the intended app will offer REST calls.
The site works properly if the HttpBasic is not included, that is the Login window is displayed. However, with HttpBasic, the site goes directly to basic authentication.
The code is directly from the cookbook.
What am I missing?
public function getAuthenticationService(ServerRequestInterface $request, ResponseInterface $response)
{
$service = new AuthenticationService();
$service->setConfig([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect'
]);
$fields = [
'username' => 'user',
'password' => 'password',
];
// Load Identifiers
$service->loadIdentifier('Authentication.Password', compact('fields'));
// Load the authenticators
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => '/users/login',
]);
$service->loadAuthenticator('Authentication.HttpBasic');
return $service;
}
As mentioned in the comments, using the form authenticator and the HTTP basic authenticator together won't work overly well, this is due to the fact that the authentication service won't stop executing all loaded authenticators, unless one of them returns a response that indicates successful authentication.
This means that you'd always be presented with the authentication challenge response, and never see your login form. Only the actual authentication part would work in that constellation, ie directly sending your login credentials as form data to the login endpoint.
If you don't actually need the basic auth challenge response that is preventing you from accessing the login form, then you could use a custom/extended authenticator that doesn't cause a challenge response to be returned, which should be as simple as overriding \Authentication\Authenticator\HttpBasicAuthenticator::unauthorizedChallenge():
src/Authenticator/ChallengelessHttpBasicAuthenticator.php
namespace App\Authenticator;
use Authentication\Authenticator\HttpBasicAuthenticator;
use Psr\Http\Message\ServerRequestInterface;
class ChallengelessHttpBasicAuthenticator extends HttpBasicAuthenticator
{
public function unauthorizedChallenge(ServerRequestInterface $request)
{
// noop
}
}
$service->loadAuthenticator(\App\Authenticator\ChallengelessHttpBasicAuthenticator::class);
Also not that you might need to add additional checks in case your application uses the authentication component's setIdentity() method, which would cause the identity to be persisted in the session, even when using stateless authenticators. If you don't want that, then you'd need to test whether the successful authenticator is stateless before setting the identity:
$provider = $this->Authentication->getAuthenticationService()->getAuthenticationProvider();
if (!($provider instanceof \Authentication\Authenticator\StatelessInterface))
{
$this->Authentication->setIdentity(/* ... */);
}

Adding SignalR service as a singleton and then adding redis to it

Hello i have an app up and running using orleans and signalR and i use a HubConnectionBuilder to initialize my SignalRClient like this
public async Task<HubConnection> InitSignalRCLient()
{
Program.WriteConsole("Starting SignalR Client...");
var connection = new HubConnectionBuilder()
.ConfigureLogging(logging =>
logging
.AddProvider(new LogProvider(Log.logger, new LogProviderConfiguration
{
Category = LogCategory.SignalR,
Level = LogLevel.Warning
}))
)
.WithUrl(Configuration.GetConnectionString("SignalRInterface"))
.Build();
And then i add the service as a singleton in the configure service
services.AddSingleton(SignalRClient)
The problem is now that i want to use redis as a backplane to this and i am having issues adding the redis service to my current way of using SignalR
like this doesn't work
services.AddSingleton(SignalRClient).AddStackExchangeRedis();
according to the documentation https://learn.microsoft.com/en-us/aspnet/core/signalr/redis-backplane?view=aspnetcore-2.2 it wants you to add it like
services.AddSignalR().AddStackExchangeRedis("<your_Redis_connection_string>");
but that doesn't work with how i use SignalR. Is there anyway to get my implementation to work or anyone got any advice on how to tackle this?
Try to add in ConfigureServices this:
services.AddDistributedRedisCache(option =>
{
option.Configuration = Configuration.GetConnectionString(<your_Redis_connection_string>);
});
services.AddSignalR().AddStackExchangeRedis(Configuration.GetConnectionString(<your_Redis_connection_string>));
Also add this in Configure
app.UseSignalR(routes =>
{
routes.MapHub<your_Hub>("/yourHub");
});
And don't forget add abortConnect=False in connectionStrings

Lumen 5.5 Session store not set on request

I use vue-authenticate (https://github.com/dgrubelic/vue-authenticate) to create two kinds of connection on our web service, the first method is the connection to his account, the second method is the addition of account when connected.
I use Lumen (by Laravel) for backend and connection management in PHP.
Only sessions are not available under Lumen, how do I store temporary credentials?
use League\OAuth1\Client\Server\Twitter;
public function login(Request $request)
{
try {
$this->server = new Twitter([
'identifier' => $this->key,
'secret' => $this->secret,
'callback_uri' => $request->get('redirectUri'), // Variable getted from POST
]);
if(empty($request->get('oauth_token'))) {
$temporaryCredentials = $this->server->getTemporaryCredentials();
$request->session()->put('temporary_credentials', serialize($temporaryCredentials)); // Session doesn't works
return response()->json([
'oauth_token' => $temporaryCredentials->getIdentifier(),
'oauth_token_secret' => $temporaryCredentials->getSecret(),
], 200);
} else {
// I must have oauth_token here with session
}
} catch (\Exception $e) {
return response()->json($e->getMessage(), 500);
}
}
I think you just misunderstood the concept of Web Service (API). API is not a stateful application, rather it's a stateless, means no session available for each request. So, in major API framework, session is not supported (officially). To handle your problem, you can store your temporary credentials in a database or maybe in a cache (with TTL, eg: 60 minutes), like this:
$requestIdentifier = $request->getClientIdentifier(); // YOU SHOULD IMPLEMENT THIS METHOD
Cache::put($requestIdentifier, $temporaryCredentials, 60);
To retrieve your cache just use:
$temporaryCredentials = Cache::get($requestIdentifier);
Here I give you some idea, when you implement getClientIdentifier, you can force the client to send a unique key inside your header, like:
axios.post('http://somewhere', {
headers: {
'x-request-identifier': UNIQUE_ID
}
})
In your API:
$requestIdentifier = $request->header('x-request-identifier');