Use sessions in laravel APIs - php

To create a Two-factor SMS verification in larvel 5.5 and via dingo package, I follow this Simplified workflow:
First check isTwoFactorActive is true or false in your login function if its true send SMS and give the response to get SMS code which is received. If its false directly return token.
Route::post('auth/login', function () {
$credentials = Input::only('email', 'password');
if ( ! $token = JWTAuth::attempt($credentials) )
{
// return the 401 response
return Response::json(['error' => 'invalid_credentials'], 401);
}
if(Auth::user()->isTwoFactorActive) {
$code = rand(1000,9999); //generate sms code
$send_sms = SendSMS($code,Auth::user()->phone); //write your own code here to send SMS to user mobile
$data= collect(array('sms_code'=>$code,'token'=>$token)); // save sms_code and token in an array
Session::push(Auth::user()->id, $data); // save array into session.
return Response::json(array('login_status'=>'success','user_id'=>Auth::user()->id,'sms_required'=>'yes'));
} else {
return Response::json(array('login_status'=>'success','token'=>$token));
}
});
Now on front end check the response if the token present, then go ahead and show homepage or show enter SMS code screen and capture the SMS code in a form and then post the details to this API again.
Route::post('sms/verification', function () {
$user_id = Request::input('user_id');
$code= Request::input('code');
$data = Session::get($user_id);
if($data->sms_code == $code) {
return Response::json(array('status'=>'success','token'=>$data->token));
} else {
return Response::json(array('status'=>'failed','msg'=>'Invalid sms code!'));
}
});
As you can see I used session to store created token to send it after successful two-factor authorization. But seem we can not use session in laravel and APIs.
what can I do in this case?

The Laravel API default setup doesn't include session. But I believe you can add them manually. Here is a link I quickly found.
Laravel 5.3 - How to add Sessions to `API` without CSRF?
But the Laravel documentation for Sessions and Middleware may also be useful.

I'm using Laravel 8 and this changes worked very well for me:
Find your App\Http\Kernel.php and make following changes to 'api' guard value of $middlewareGroups:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

Related

language are not changing in laravel localization

How do I solve my problem ?
Let me tell that how can i change language in laravel web page
List item
Route::get('/{lang?}', function($lang = null){
App::setLocale($lang);
return view('frontend.home');
});
This code will work only for the current request, so when you return the view, it's a new request.
So what you have to do is to persist the language, for each request, so we'll use middleware.
First of all, create a middleware, to check the which language is set, as follows,
public function handle(Request $request, Closure $next)
{
if (session()->has("lang")) {
App::setLocale(session()->get("lang"));
}
return $next($request);
}
What this code will do is, check if the session has the lang_code exists, so it will set it.
After that, you need to add this middleware to your $middlewareGroups, inside app\Http\Kernel.php, as follows :
<?php
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\Language::class, # This is the line we will add
],
Then finally we'll tweak a little bit your function to become like this :
Route::get('/{lang?}', function($lang = null){
App::setLocale($lang);
session()->put('lang', $lang);
return view('frontend.home');
});
Like this, within each request, the middleware will verify the language, and set it.

Set cookie when the website opened first time in Laravel

I'm writing localization with cookies. Localization works fine with cookies, but I want to set default language for someone who opened the website first time. So, I created a middleware for that and registered in route middlewares:
public function handle(Request $request, Closure $next)
{
get_cookie('lang') ?? set_cookie('lang', 'az');
return $next($request);
}
get_cookie and set_cookie comes from custom helpers:
if (!function_exists('get_cookie')) {
function get_cookie($name)
{
return Cookie::get($name);
}
}
if (!function_exists('set_cookie')) {
function set_cookie($name, $value)
{
return Cookie::queue($name, $value, time() * 360 * 60);
}
}
The issue is when I first opened website it sets cookie, but it isn't shows website with cookie. But, second time I opened website it shows the website with cookie.
I googled it and found this in stack overflow:
if($request->hasCookie('lang')) {
return $next($request);
}
$response = $next($request);
return $response->withCookie(cookie()->forever('lang', 'az'));
But it didn't work out for me, I think it works for old versions of laravel. I'm assuming that, I'm using middleware in the wrong way, but I didn't figured out. Any advice will be appreciated.
EDIT:
I changed middleware's place $routeMiddleware to $middlewareGroups. I think AddQueuedCookiesToResponse middleware has to be in that middleware list. But, it doesn't seem to be help. Maybe, there has to be another middleware in the list?
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SetLanguage::class,
],
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
I didn't managed to find a way out to get it work with cookies. So, I just simply used sessions instead of cookies. So, in the middleware I set lang variable to session.
public function handle(Request $request, Closure $next)
{
if(!$request->session()->get('lang')) {
session(['lang' => 'az']);
}
return $next($request);
}

Laravel custom LoginController doesn't persist session

My system is built using Vue and Laravel. My requirement is to send a one-time password to the User on each login. Hence, I have created a custom login API controller. I can create a Passport token and send it back to the User. However, I also want Laravel to persist the user session, but, that's not happening. Below is my code for reference:
Api\LoginController.php
public function login(Request $request)
{
$data = $request->all();
if (auth()->attempt($data)) {
$token = auth()->user()
->createToken(config('app.passport_token_key'))->accessToken;
$user = User::whereEmail($data['email'])->first();
// It is not persisting. When I redirect the user to the next page,
// it doesn't pass the auth middleware.
auth()->login($user);
if ($this->sendVerificationCode($user)) { // Send One Time Password
return [
'token' => $token,
];
}
}
return ['error' => true, 'message' => 'Unauthorized'];
}
web.php (route file)
use App\Http\Controllers\HomeController;
Route::middleware(['auth'])->group(function () {
Route::get('/home', [HomeController::class,
'index'])->name('home');
});
Auth::routes();
api.php (route file)
use App\Http\Controllers\Auth\LoginController;
Route::post('login', [LoginController::class, 'login'])
->name('api:login');
I am calling the login API using Vue JS (Axios).
Once the user is logged in (via API), my end goal is that web, and api both guards should know the user session. However, only the api guard can authenticate me (not the web guard).
The problem is, the StartSession middleware is only present in the web routes. Check your app/Http/Kernel.php file, it should be something like this:
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
// NOTE the StartSession Here
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
If you want to use the session, you should add the middleware to your login route.
Inside the $routeMiddleware array inside app/Http/Kernel.php add:
'session.start' => \Illuminate\Session\Middleware\StartSession::class
Then add the middleware to the logi route in your routes/api.php file:
Route::post('login', [\App\Http\Controllers\Api\LoginController::class, 'login'])
->name('api:login')
->middleware('session.start');

Laravel 7.x Sanctum (SPA) with Vuejs always returns 401 Unauthorized

So it's been several hours I couldn't figure out the issue even reading through and trying any possible solution available on the internet. I'm using Laravel 7.x with Vue js and struggling with Sanctum SPA authentication.
Login request works fine which is using Auth::routes() defined in web.php
but, any requests made to APIs defined in api.php under auth:sanctum middleware returns 401. For example, call to fetch the user object fails with status 401:
Here is the Request Header:
This is web.php
This is api.php
Here is the stateful object in sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1,127.0.0.1:8000')),
On vue.js side, I've set the withCredentials flag to true:
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.withCredentials = true;
in cors.php, suports_credentials flag is also set to true
and, here is my Kernel.php
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
I had the same issue and I couldn't find any answer but after troubleshooting I got it working.
Your issue is that you're accessing it through localhost port 8000, but in your stateful param under sanctum config there is no localhost:8000 (even localhost pointing to 127.0.0.1). The config uses $_SERVER['SERVER_NAME'] so it actually looks for the exact content while accessing it.
A simple fix below:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', implode(',', [
'localhost',
'localhost:8000',
]))),
I not use Sanctum but use Vuejs Laravel and Axios and I notice that you need to add to all axios protected URL the header Authorization
Bearer YOURTOKENKEY
On my ap.js I use
import jwtToken from './helpers/jwt-token'
axios.interceptors.request.use(config => {
config.headers['X-CSRF-TOKEN'] = window.Laravel.csrfToken
config.headers['X-Requested-With'] = 'XMLHttpRequest'
if (jwtToken.getToken()) {
config.headers['Authorization'] = 'Bearer ' + jwtToken.getToken()
}
return config;
}, error => {
return Promise.reject(error);
});
and my JWTTOKEN js file
export default {
setToken(token) {
window.localStorage.setItem('jwt_token', token);
},
getToken() {
return window.localStorage.getItem('jwt_token');
},
}
So all request by axios send Authorization header with Bearer + token
I hope this help you

Session data not preserved after redirection

I'm trying to implement some custom flash messages and I'm having some issues with the session data being destroyed after a redirect.
Here's how I create my flash messages :
flash('Your topic has been created.');
Here's the declaration of the flash() function :
function flash($message, $title = 'Info', $type = 'info')
{
session()->flash('flash', [
'message' => $message,
'title' => $title,
'type' => $type,
]);
}
And here is how I'm checking the session/displaying the flash messages, using SweetAlerts. This code is included at the bottom of the main layout file that I'm extending in all my Blade templates.
#if(Session::has('flash'))
<script>
$(function(){
swal({
title: '{{ Session::get("flash.title") }}',
text : '{{ Session::get("flash.message") }}',
type : '{{ Session::get("flash.type") }}',
timer: 1500,
showConfirmButton: false,
})
});
</script>
#endif
The code above will work if I call the flash() function before displaying a view, like so :
public function show($slug)
{
flash('It works!');
return view('welcome');
}
However, it will not work if I call it before doing a redirect to another page, like so :
public function show($slug)
{
flash('It does not work');
return redirect('/');
}
Why is the session data lost on redirect? How can I make it persists so that I can display my flash message?
I found out that it is necessary to apply the web middleware on all routes. Drown has mentioned to do so, but since March 23st 2016, Taylor Otwell changed the default RouteServiceProvider at https://github.com/laravel/laravel/commit/5c30c98db96459b4cc878d085490e4677b0b67ed
By that change the web middleware is applied automatically to all routes. If you now apply it again in your routes.php, you will see that web appears twice on the route list (php artisan route:list). This exactly makes the flash data discard.
Also see: https://laracasts.com/discuss/channels/laravel/session-flash-message-not-working-after-redirect-route/replies/159117
It turns out that with Laravel 5.2, the routes have to be wrapped in the web middleware for the session to work properly.
This fixed it :
Route::group(['middleware' => ['web']], function () {
// ...
Route::post('/topics/{slug}/answer', 'PostsController#answer');
Route::post('/topics/{slug}/unanswer', 'PostsController#unanswer');
Route::post('/topics/{slug}/delete', 'PostsController#delete');
});
Please check APP/kernel.php
\Illuminate\Session\Middleware\StartSession::class,
is define multiple times
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Session\Middleware\StartSession::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
You can comment any one or delete it. We need to define one time only.
The issue i had was Session::save() preventing swal from showing after redirect.
so you need to remove Session::save() or session()->save(); from middleware
With Laravel 5.2.34, all routes are using web middleware by default.
Therefore, change this:
Route::group(['middleware' => ['web']], function () { // This will use 2 web middleware
// ...
Route::post('/foo', 'FooController#foo');
});
To this:
Route::group([], function () { // This will use the default web middleware
// ...
Route::post('/foo', 'FooController#foo');
});
And then in your controller you could use:
class FooController extends Controller
{
...
public foo()
{
...
return redirect('/foo')->withSuccess('Success!!');
// or
return redirect('/foo')->with(['success' => 'Success!!']);
}
...
}
Redirect with flash data is done like this:
redirect("/blog")->with(["message"=>"Success!"]);
In early Laravel 5.2 versions, all of your Flash and Session data are stored only if your routes are inside web middleware group.
As of Laravel 5.2.34, all routes are using web middleware by default. If you will put them into middleware web group again, you will apply web middleware on your routes twice - such routes will be unable to preserve Flash or Session data.
Check your App\Kernel.php file.
There may be multiple lines of \Illuminate\Session\Middleware\StartSession::class,
Comment one from $middlewareGroups.
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
**\Illuminate\Session\Middleware\StartSession::class,**
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
have you tired using "redirect with"
https://laravel.com/docs/5.2/responses#redirecting-with-flashed-session-data
Additional to #Harry Bosh answer,
In Laravel there an issue when Session::save() happen inside the middleware,
this make _flash session gone after redirection happen
this can be fix by using alternative :
// replace your Session::save() to this
session(['yoursessionvar' => $examplevar]); // this will save laravel session

Categories