Laravel - storing into session from service provider? - php

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

Related

Laravel - Dynamic APP_NAME config variable based on domain

How do I overwrite the APP_NAME config variable dynamically based on the domain/hostname so that I can reference it in all Controllers and Blade templates?
OR how can I create a global variable (single record from a Model) that changes dynamically based on the domain, so that it is accessible in all Controllers and Blade templates?
I have 3 domains - each for a different athletic conference/League - that share 100% the same code. Currently, I have a function in /app/Helper/Helper.php that I call from every Controller. This gets the correct League based on the domain, and allows me to send the correct set of data to each view.
// Get league info based on domain
public static function getLeague(Request $request)
{
$host = $request->getHost();
if (App::environment('production')) {
// Some fancy Substring logic to get the domain name
} else {
// Manually set $host to one of my domains for offline debugging
}
$league = League::where(['url' => $host])->first();
return $league;
}
The above works great for Views and Controllers I have built.
However, I also leverage several out-of-the-box views and email templates for User Registration, Login, Forgot Password, etc., that use APP_NAME.
Some of them (like the Login or Register screens) have a typical Controller so I can call my Helper function and just pass the League to the view. But for others, like email templates, I've tried going down the rabbit trail of Controllers and Functions that eventually produce the email content, and I cannot find where to call my Helper function and pass the League Name to the view.
An example of an email template using APP_NAME, that I cannot figure out how to pass $league to it instead of it using the config variable, is:
/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/message.blade.php
So I'm stuck trying to figure out how to set the APP_NAME (or automatically calling my Helper function and creating a global $league variable that I can use in Controllers and Blade templates).
I have tried updating 'name' in /config/app.php, but it errors when trying to call the Helper function.
<?php
use App\Helper\Helper;
//dd($_SERVER['SERVER_NAME']);
$league = $league = Helper::getLeagueWithHost($_SERVER['SERVER_NAME']);
return [
'name' => $league->name,
....
]
Error:
Fatal error: Uncaught RuntimeException: A facade root has not been set. in C:\...\vendor\laravel\framework\src\Illuminate\Support\Facades\Facade.php on line 258
This all wouldn't be a problem to set manually in each .env file, but I have an AWS CodePipeline that deploys to each domain upon GitHub push, so I'd have to update 3 .env files every time.
Thanks for any help!
I found a way to accomplish the ultimate goal, but not in the manner being requested in my initial question.
As part of the AWS CodePipeline deployment, there are "Appspec" events that fire at various times in the process (application-stop, before-install, after-install, application-start, and validate-service).
I wrote a Bash script in the "after-install" event that checks the IP of the hostname (returns the Private IP of the current Lightsail instance being deployed). And depending on the hostname IP, it changes line 2 of my .env file to the desired APP_NAME.
There is some hard-coding involved, mapping each Lightsail Private IP to an APP_NAME (meh), but as long as I do not delete and recreate a Lightsail instance, the Private IPs will remain static. The only way to avoid that would be to solve the initial question of updating the APP_NAME at runtime based on the URL/domain.
So, if someone else is trying to solve this problem at runtime when a page loads, this solution will not work for you.

How to invalidate particular session in Laravel (with user using remember me feature)

Using Laravel 5.1 & 'file' Session Driver,
I'm trying to provide facility to user to track their sessions and invalidate them anytime they wish by keeping a record of their session_id within the database. With Database, I mean, I maintain a table called user_sessions which associates user_id with their session_id (obtained by Session::getId()).
So, to invalidate Session, I tried the following code,
$sessionId = Session::getId();
Session::setId($sessionId);
Session::invalidate();
and it works perfectly fine, for the case where, where user does not uses Remember Me feature.
For the case where user uses Remember Me feature, this above code does not work, So, I additionally, tried setting remember_token field to null as specified here in this answer, but with this, all sessions of the user get destroyed including the current one.
You can simply use
use Illuminate\Support\Facades\Session;
Session::forget('YOUR_SESSION_ID');
If you want to get the current session Id:
Session::driver()->getId();
I hope it helps
Sessions are meant to be short-lived. If you want something a bit more permanent you can use some sort of a long term user settings table.
Create a table user_settings:
id (PK), user_id(FK users table), settings(BLOB?), created_at, updated_at
Add a model:
class UserSetting extends Model {
public function user() {
return $this->belongsTo(User::class);
}
}
You can also associate the user with these via :
class User extends Model {
//...
public function settings() {
$this->hasMany(UserSetting::class);
}
}
You can then get all user sessions via:
User::find($u)->settings();
When a user logs in regularly or automatically via a remember token a Login event is fired.
You can listen to this in your event service provider:
\Event::listen(\Illuminate\Auth\Events\Login::class, function ($event) {
// Here you can load the last settings in the session if you want e.g.
session(['current_settings' => $event->user->settings()->latest()->value('id') ]);
// or you can just make a new entry:
$settings = new UserSettings();
$event->user->settings()->save($settings);
session(['current_settings' => $settings->id ]);
});
Note that you will have to manually persist things that need persisting instead of just putting them in the session.

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();
}

Laravel, 2 projects in 2 domains same session

I'm creating 2 projects in 2 diferent domains domain1.tld and domain2.tld.
The domain1.tld is the main event producer page and the domain2.tld is one of its events. I want to share the same sessions (they actually share the same database and the same apache server). I tried to change the session driver to "database" and create a session table, but nothing happens, if i'm log in domain1.tld nothing happens in domain2.tld.
I really have searched in the net but i have found nothing
you can't do this in your way...
when you set session, a cookie set in browser for track stored session in server side.
if you want to share session between two domain you should share cookie between to site bot you can not do it (you can do it just in sub domains of ONE domain)
but there is a little hack :
The easiest work-around is to pass login/credential information from website A to website B and have website B set a seperate cookie. For example, after logging into website A you could have them quickly redirected to website B with an encrypted querystring. Website B could then read the information, set its own cookie, and redirect the user back to site A.
It's messy but possible.
Step 1: Set Session Driver for Shared Session Data
First, set your session driver to a database or cache that is shared across both domains. Your session driver cannot be file
Step 2: Implement Cross-Domain Session IDs
Session ids are passed around by cookies in Laravel. Since your websites are on different domains the session cookie does not transfer over. One way to solve this is to append them to the query string of all your requests like so: domain2.tld/?session_token=abcds2342
Within your code there must be some login that detects a session and then query the database/cache (your session driver) for a result. If a result is found, you set the session ID manually and start the session:
session_id('abcds2342');
session_start();
Be careful to check both the IP address and the session ID
to prevent people from guessing someone elses SessionID and thus
logging in as another person
Step 2A: To do this you can implement a custom middleware that overrides StartSession. This middleware should override getSession and before it checks for session_id in cookie, check if we have a token present in the Request. Sample code below:
<?php
namespace App\Http\Middleware;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Http\Request;
use App\SessionShare;
use Closure;
class StartSessionWithSharer extends StartSession
{
/**
* Get the session implementation from the manager.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Session\SessionInterface
*/
public function getSession(Request $request)
{
$session = $this->manager->driver();
/**
* Check if we can find a valid session token from saved records
*/
if($request->get('session_token') && !empty($request->get('session_token'))) {
$sessionShare = SessionShare::valid()->whereToken($request->get('session_token'))->first();
if($sessionShare)
$session_id = $sessionShare->session_id;
}
/**
* Fallback to session in browser
*/
if(!isset($session_id) || !$session_id)
$session_id = $request->cookies->get($session->getName());
$session->setId($session_id);
return $session;
}
}
Step 2B: Then create a custom service provider to override SessionServiceProvider like so:
<?php namespace App\Providers;
class CustomSessionServiceProvider extends \Illuminate\Session\SessionServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerSessionManager();
$this->registerSessionDriver();
$this->app->singleton('App\Http\Middleware\StartSessionWithSharer');
}
}
And then remove the old SessionServiceProvider from config/app.php and instead use above.
Step 2C: Then create your App\SessionShare model for the table to store session IDs. Also, the above code doesn't take care of checking IP address so you would have to add that in to make it secure and prevent brute force attacks
Step 2D: Oh and finally don't forget to append the get parameter for session_token for all your requests
Note that the above implementation is for a database session driver. Of course, you can do this for a cache driver too. The only thing that would change is the model implementation (step 2C) to validate the session

cakephp database logger - how to access controller variable?

I am following the cakephp documentation to implement the database logger function. I got everything working after creating the logger class in app/Lib/Log/Engine/DatabaseLogger.php
and add these lines to bootstrap
CakeLog::config('otherFile', array(
'engine' => 'DatabaseLogger',
'model' => 'LogEntry',
// ...
));
After this I can log to the database by calling
CakeLog::info(message);
Everything works fine, but then I ran into problem trying to automatically log the user's username and ip address. I have searched for many solutions onlien but I do not seems to be able to find an answer. Is it possible to access the controller in the DatabaseLogger class?
You don't need access to the controller
The information you need is effectively global, you don't need to access the controller object to get it.
Client IP
You can use the Router::getRequest method to get the current request object and retrieve the client IP from that:
$request = Router::getRequest();
$ip = $request->clientIp();
Or simply use the env method:
$ip = env('REMOTE_ADDR');
Current User's name
For this, just use the static session interface:
$name = CakeSession::read('Auth.User.name');
This will hopefully get you going in the right direction. I'm currently in the process of migrating some of my CakePHP apps up to 2.2. I'm not 100% certain on this, but it seems to be about the way to go about this. I am thinking about using database logging and I like your idea so let me know how this works out. If it doesn't, provide feedback and I'll dig deeper. ;)
Create your LogEntry model (database table) to include fields for the username and the ip address.
Create an AppController. In the beforeFilter method place the user's ip address in their session. The CakeRequest object will contain their IP address.
$this->request->clientIp();
In your beforeSave() method for that model, you will collect the username and IP address.
To access the username field (assuming you are using Auth component and clientIp as the key to their ip address):
App::import('Component', 'Session');
$Session = new SessionComponent();
$user = $Session->read('Auth.User');
$ip = $Session->read('clientIp');
Assign these to the right fields so that they will be saved.

Categories