I am using a Laravel Application as a backend and wish to set the database connection dynamically (and keep it until the page is refreshed) through an axios request which will contain the database to use and the credentials.
For that purpose, I am storing the received DB configuration in a session and set the env variables with it in the constructor of whichever controller I am trying to use.
Here are the default database settings in .env :
DB_DATABASE=database1
DB_USERNAME=username1
DB_PASSWORD=password1
However, the issue seems to be that the session is not being kept alive, as each sent request contains a different session ID and therefore returns an Access denied error whenever I try to interact with the Database because the session variables are undefined.
Here is how the request is sent from the client :
axios
.post("https://data-test.io/api/Setconnection", {
database: "database2",
username: "username2",
password: "password2",
})
.then((result) => {
// console.log(result);
// do stuff here
});
This is how I store the DB connection in the session :
class RootController extends Controller
{
public function setConnection(Request $request){
session(['database' => $request->database]);
session(['username' => $request->username]);
session(['password' => $request->password]);
return $request->session()->all(); // this returns the correct values
}
}
And I set the route in api.php like so :
Route::post('/Setconnection',[RootController::class, 'setConnection']);
Then, on all subsequent requests, I set the connection in the constructor this way :
public function __construct() {
Artisan::call('config:cache');
Config::set('database', session('database'));
Config::set('username', session('username'));
Config::set('password', session('password'));
}
public function getConfig(){
return [session('database'),session('username'),session('password')];
// this returns an array of undefined elements.
}
Am I making a mistake here or is this not how I am supposed to set database connections dynamically? If not then what is the best way do so ?
As Tim Lewis said, APIs are supposed to be "Stateless" which no previously stored data that may be utilized in every transaction/request. If you don't want to use a database to store the dynamic database credentials, you need to save dynamic database credentials on the client side and send them each request.
But if you really want to create "Stateful" which can use session in the API you can follow these instructions.
Then to change database config you should use Config::set('database.connections.dbdrivername.configname','configvalue') and there is no need Artisan::call('config:cache')
an example:
Config::set('database.connections.mysql.database', session('database'));
Config::set('database.connections.mysql.username', session('username'));
Config::set('database.connections.mysql.password', session('password'));
You should really know what you are doing because it may have a big security hole.
I have needed this in the past for a multi-tenant application, and my solution was this:
DB::purge('mysql');
Config::set('database.connections.mysql.database', $request->database);
Config::set('database.connections.mysql.username', $request->username);
Config::set('database.connections.mysql.password', $request->password);
DB::reconnect('mysql');
Without the DB::purge and the DB::reconnect, the changes would not be applied to the connection.
With that out of the way, I too think this is a super risky practice. You are giving the world your database name and credentials.
Related
Is it right to get Session data in a store function and store them into db?
public function store(){
...
$idgroup = Session::get('invitation_userid')];
...
}
Or need a store function always a Request Object?
public function store(Request $request){
...
$idgroup = $request('idgroup');
...
}
In both functions is of course a validation part for the input data.
Both approaches are fine, but you should use them appropriately to your use case, I prefer to use the Request data. The main difference is that if u store that inside the Session it will be available application wide, while if u send inside Request it will be available inside the method only
This depends entirely on the context of what your controller is actually named, how this data is being used and why you are not using a database session driver in the first place if you want to do this.
You could simply use the database driver for the session:
https://laravel.com/docs/5.7/session#introduction
It also depends on what your controller is named if you strictly want to follow restful routes:
https://gist.github.com/alexpchin/09939db6f81d654af06b
To answer the second question you don't always need a Request object in your store action. Most of the time you won't even see a Request object because you are simply creating an entirely new resource.
The Global Session Helper
You may also use the global session PHP function to retrieve and store data in the session. When the session helper is called with a single, string argument, it will return the value of that session key. When the helper is called with an array of key / value pairs, those values will be stored in the session:
$value = session('key');
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();
}
How can I set at run time a global variable that is visible for all request? After login user, I need to save the data of DB connection and connect to his database in all request (with a middleware). I just proved $GLOBALS of PHP and app()->singleton() of Laravel, but, in both case, after the request I lose the content of variable.Why?
And how can I resolve? I don't want put the db data to session or cache.
If it is not already stored on the users' object then I'd probably store the database connection info with the user. Then I'd write a middleware to create the connection if a user is logged in. Something like:
public function handle($request, Closure $next)
{
if ($request->user()) {
$connection = new mysqli('localhost', $user->dbUsername, $user->dbPassword, $user->dbName);
$request->merge("connection" => $connection));
return $next($request);
}
return redirect('login'); //the user isn't signed in
}
Then later in your code you would use this connection by:
$this->connection = $request->connection;
Do note that there's no error handling for if the new connection fails and I haven't tested the code. But this is probably enough code to get you started.
I am using GeoIP package to get the user's IP and translate it into a zipcode. I don't want to do that for every request that the user is making but rather do a one time IP to zipcode, store it into session and then when I need to use it just check if the zipcode exists inside the session.
I tried to place the code inside AppServiceProvider#boot but it does not work. It is not remembered into the session. I tried inside routes but not working as well.
edit
The code inside boot method of appserviceprovider. This is just a test.
If (! Session()->has ('zipcode'))
Session(['zipcode' => geocodeZipcode()]);
The problem is that this runs everytime since the zipcode is not persisted in the session. The if is never false from my tests so far.
Where do I need to put the code to store the zipcode into the session and have it remembered even if the user is not logged in?
I basically need something like this:
1- User accesses a page on the server for the first time (any page)
2- I get the user IP and translate it to a zipcode
3- I store the zipcode into the session
4- For every other request the user makes I check if the zipcode exists into the session. If not I execute step 2.
5- Use the zipcode for its purpose
Where should I place the step 2 and 3?
In Laravel the session is initialized via middleware, and all the middlewares execute after the service providers boot phase
This is the reason why in your service provider you can't access the session: it has not been initialized yet
You should place your steps 2 and 3 in a middleware:
class ZipCodeMiddleware
{
public function handle( Request $request, Closure $next )
{
//ZIP CODE NOT FOUND IN SESSION: CREATE IT AND STORE
if ( ! Session::has( 'zipcode' ) )
{
//get ip and translate to zip
//store zip in the session
}
//use zip code here or access it later from Session
return $next($request);
}
}
Once you've stored the zip code in the session, you can access it from a controllers directly from the session, or, you could instance a class in the middleware and re-access it later with:
//use zip code here or access it later from Session
$zipClass = new ZipClass( $zipCode );
App::instance( ZipClass::class, $zipClass );
This way you can auto-inject the ZipClass depencency in your controllers and Laravel will give you back the $zipClass instance you built previously in the middleware
I was hoping someone could help me with a question I've come up on.
I have a Session object that handles storage of general session data, I also have a Authentication object which validates a users credentials.
Initially I passed the desired Authentication class name to my Session object then had a login method that created an instance of the Authentication object and validate the credentials. I stored the result of this validation in a Session variable and made it available via a getter. The user data was also stored in the Session for later use. In addition to all this, I have a logout method, which removes the user data from the Session and thus logging the user out.
My question is what role should the Session object play in users logging into their account?
And what other ways might one suggest I go about handling user login, as it stands right now I feel as though I'm getting too much wrapped up in my Session object.
Simply calling your authenticate method should trigger logic within Auth to store the proper data in the session (or some other data store) and Auth should also be used exclusively to retreive/revoke this info. So using the example form your comment it might be:
class Auth {
public static function authenticate($identity, $pass)
{
// do lookup to match identity/pass if its good then
/* assume $auth is an array with the username/email or
whatever data you need to store as part of authentication */
Session::set('auth', $auth);
return true;
// if auth failed then
Session::set('auth', array('user'=>'anonymous'));
return false;
}
public function isAuthenticated()
{
$auth = Session::get('auth');
if(!$auth)
{
return false;
}
return (isset($auth['user']) && $auth['user'] !== 'anonymous');
}
}
[...] as it stands right now I feel as
though I'm getting too much wrapped up
in my Session object.
And id agree. Idelaly for authentication/credentials you shoudl only be interacting with the Auth/Acl object(s). They would then utilize the session as stateful store... but you shouldnt care that its even stored in session. The code utilizng the Auth/Acl object(s) should be completely unaware of this fact.
For example:
//Bad
if($session->get('authenticated', 'auth'))
{
// do stuff
}
// also bad
if(isset($_SESSION['authenticated']))
{
// do stuff
}
//Good
if($auth->isAuthenticated())
{
// do stuff
}
// inside $auth class it might look like this
public function isAuthenticated()
{
$store = $this->getSotrage(); // assume this returns the $_SESSION['auth']
return isset($store['authenticated']);
}
The session is a good place for holding user data that you want to have managed in some sort of state across various pages or if you need a fast accessible way to get at it without hitting the database. It is a bad idea to keep secure information (re: passwords/etc) in the session, but rapid access information like username, name, email address, preferences, etc is all good data to put in the session. Try to keep it simple though.
Keep in mind though, that the session (or related cookie) should only ever be used for identification. It should not be used for authentication.
Authentication object is a good method. Make sure that it only holds secure information as long as it needs to and that it has all the necessary functions available to keep sensitive data protected.