I am new to laravel and have been working with cashier for a web app I am developing. In my app a user creates their account and company and they are allowed to use the app. Because a company can have many users, I need cashier to check if the company has a subscription or not.
In the cashier docs using Stripe I have set it up where no credit card is required up front and they can use the system for 14 days until being prompted for a credit card.
So far I have successfully created the cashier columns on my companies table and added the subsctiption table according to the docs.
add_cashier_table_fields.php migration file:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCashierTableFields extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
//
Schema::table('companies', function ($table) {
$table->string('stripe_id')->nullable();
$table->string('card_brand')->nullable();
$table->string('card_last_four')->nullable();
$table->timestamp('trial_ends_at')->nullable();
});
Schema::create('subscriptions', function ($table) {
$table->increments('id');
$table->integer('company_id');
$table->string('name');
$table->string('stripe_id');
$table->string('stripe_plan');
$table->integer('quantity');
$table->timestamp('trial_ends_at')->nullable();
$table->timestamp('ends_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
//
}
}
Then in my company model I added the Billable trait as suggested.
Company.php - model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Cashier\Billable;
class Company extends Model
{
use Billable;
protected $dates = [
'trial_ends_at',
'subscription_ends_at'
];
protected $fillable = [
'company_name',
'trial_ends_at',
'subscription_ends_at'
];
protected $cardUpFront = false;
public function users()
{
return $this->hasMany(\App\User::class);
}
}
Now in my RegisterController.php file I have it where when a company is created it where it will Carbon the date 14 days from that day and add to 'trial_ends_at' column
Auth/RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use App\Company;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;
use Carbon\Carbon;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'company_name' => 'required|unique:companies,company_name',
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
$company = \App\Company::create([
'company_name'=> $data['company_name'],
'trial_ends_at' => Carbon::now()->addDays(14), //Collect CC# 14 days from now
]);
$user = $company->users()->create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user->attachRole(1); //Admin role
return $user;
}
}
I am trying to check if the current subscription is within its trial period or not using
if ($company->onTrial()) {}
I figure since I need to restrict access to the whole system (besides the registration pages) I should use a middleware to check for subscription status. So I created Subscription.php middleware with the following:
<?php
namespace App\Http\Middleware;
use Closure;
use App\User;
use App\Company;
use Illuminate\Support\Facades\Auth;
class Subscription
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()){
//dd($request->user);
; $companyID = Auth::user()->company_id;
$company = Company::find($companyID);
dd($company->onTrial());
if($company->onTrial()){
return redirect('order');
}
}
return $next($request);
}
}
Question: What is the best way to attach cashier to a company (instead of per user) and restrict access to the system if the subscription is not active?
When I var_dump($company->onTrial()) that it always prints false? I made sure the date was from earlier this year so I should be past the trial time but no matter if I am in the trial timeframe or not it always prints false. Is this the best approach for what I am trying to do? Apologies for all the code, I wanted to give everyone the whole picture because there is little information about this online.
The only thing I can see different from other posts about this topic is my Company model extends Model and not Authenticatable. I verified Subscription was added to my kernel.php file and the middleware is register in my routes file.
Turns out this is working. When I change my dates manually in the database to be outside of my trial it returns false, likewise if I am in my trial period it returns true. In my case I needed to check onTrial() and also if the current url was localhost:8000/order - if not then the app redirects them to that order page until they enter their card info. Im posting my final middleware here in case anyone in the future has a similar situation and needs the functioning code. (Still don't know if this is the best approach but it works)
<?php
namespace App\Http\Middleware;
use Closure;
use App\User;
use App\Company;
use Illuminate\Support\Facades\Auth;
class Subscription
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()){
//dd($request->user);
$companyID = Auth::user()->company_id;
$company = Company::find($companyID);
//dd($company->onTrial());
if(!$company->onTrial() && $request->path() != 'order'){ //If trial has expired redirect to order page
return redirect('order');
}
}
return $next($request);
}
}
Related
I am trying to update an old (5.2) laravel installation to the newest one (9.2) and most is working except the authentication part.
I already installed laravel/ui, I have an authentication middleware, route and controller but for some reason I get:
Invalid route action: [App\Http\Controllers\Auth\AuthController].
My Authenticate.php middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}
My route in web.php :
Route::get('/login', 'App\Http\Controllers\Auth\AuthController')->name('login');
And my AuthController:
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Topsite\Dataroom\Models\LogEntry;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\RegistersUsers;
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use RegistersUsers, ThrottlesLogins;
/**
* Where to redirect users after login / registration.
*
* #var string
*/
protected $redirectTo = '/';
/**
* The custom login view.
*
* #var string
*/
protected $loginView = 'pages.login';
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'logout']);
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
/**
* Send the response after the user was authenticated.
*
* #param \Illuminate\Http\Request $request
* #param bool $throttles
* #return \Illuminate\Http\Response
*/
protected function handleUserWasAuthenticated(Request $request, $throttles)
{
if ($throttles) {
$this->clearLoginAttempts($request);
}
if (method_exists($this, 'authenticated')) {
return $this->authenticated($request, Auth::guard($this->getGuard())->user());
}
LogEntry::create([
'account_id' => Auth::id(),
'message' => 'Ingelogd'
]);
return redirect()->intended($this->redirectPath());
}
}
I thought maybe I need to set a method in the route so I changed the route to:
Route::get('/login', 'App\Http\Controllers\Auth\AuthController#handleUserWasAuthenticated')->name('login');
But this gives:
Too few arguments to function App\Http\Controllers\Auth\AuthController::handleUserWasAuthenticated(), 1 passed in C:\xampp\htdocs\dataroom.website.nl.test\vendor\laravel\framework\src\Illuminate\Routing\Controller.php on line 54 and exactly 2 expected
I think the $throttles variable is empty but what needs to be passed there? Or maybe I am thinking wrong alltogether and the fix is something else?
I have using Laravel 6.x as a backend with an external (different domain) Vue frontend and do not have register user functionality. The way I register users is by importing batches of users using the Maatwebsite/Laravel-Excel package - which works great.
So when each user is created a job is created by sending each user an email verification link, which when they login for the first time they will need to change their password and simultaneously their email gets marked as verified - which also should work fine.
The problem is that with the already created factory of users, who have their email_verified_at field filled - and the newly imported users - I cannot login as the custom EnsureEmailApiIsVerified middleware does not have access to the $request->user(). I figured out that I can specify the auth guard of 'api' such as $request->user('api') which then can pick up the user, but only of their Bearer token (using Laravel Passport) is sent with the request.
This does make sense as how else would the system know who the request user is without some identifier such as the token. But then how does the standard 'implements MustVerifyEmail' on the User model and subsequent standard 'EnsureEmailIsVerified' middelware on the web routes pick up the $request->user()?
It would stand to reason that either both (standard and my custom) middleware should have access to the $request->user() or both should not.
Now I have had to modify and bring out quite a few framework controllers into my App\Http directory but I have copied them almost verbatim just changing a few things to ensure it works with my API routes - because setting the default guard to 'api' instead of 'web' in config/auth.php had not used it as a default throughout my controllers as thought to be.
So here are the steps I followed:
Created a custom middleware and attached it to the entire 'api' middleware group in App\Http\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' => [
'throttle:60,1',
'bindings',
'verifiedapi',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'role' => \App\Http\Middleware\HasRole::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'verifiedapi' => \App\Http\Middleware\EnsureApiEmailIsVerified::class,
];
Then here is that custom 'EnsureApiEmailIsVerified' middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use App\Http\Controllers\API\Auth\MustVerifyApiEmail;
class EnsureApiEmailIsVerified
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user('api') ||
($request->user('api') instanceof MustVerifyApiEmail &&
! $request->user('api')->hasVerifiedEmail())) {
return abort(403, 'Your email has not yet been verified.');
}
return $next($request);
}
}
You will see that references an instance of my custom 'MustVerifyApiEmail' which is a trait that is used on the User Model, with the only diversion from the standard trait being the public function 'sendApiEmailVerificationNotification' as such:
<?php
namespace App\Http\Controllers\API\Auth;
use App\Notifications\VerifyApiEmail;
trait MustVerifyApiEmail
{
/**
* Determine if the user has verified their email address.
*
* #return bool
*/
public function hasVerifiedEmail()
{
return ! is_null($this->email_verified_at);
}
/**
* Mark the given user's email as verified.
*
* #return bool
*/
public function markEmailAsVerified()
{
return $this->forceFill([
'email_verified_at' => $this->freshTimestamp(),
])->save();
}
/**
* Send the email verification notification.
*
* #return void
*/
public function sendApiEmailVerificationNotification()
{
$this->notify(new VerifyApiEmail);
}
/**
* Get the email address that should be used for verification.
*
* #return string
*/
public function getEmailForVerification()
{
return $this->email;
}
}
This new 'sendApiEmailVerificationNotification()' notifies the $request-user('api') with a custom VerifyApiEmail Notification, as such:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Config;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class VerifyApiEmail implements ShouldQueue
{
use Queueable;
/**
* The callback that should be used to build the mail message.
*
* #var \Closure|null
*/
public static $toMailCallback;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$verificationUrl = $this->verificationUrl($notifiable);
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
}
return (new MailMessage)
->subject(Lang::get('Verify Email Address'))
->line(Lang::get('Please click the button below to verify your email address.'))
->action(Lang::get('Verify Email Address'), $verificationUrl)
->line(Lang::get('If you did not create an account, no further action is required.'));
}
/**
* Get the verification URL for the given notifiable.
*
* #param mixed $notifiable
* #return string
*/
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.api.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
/**
* Set a callback that should be used when building the notification mail message.
*
* #param \Closure $callback
* #return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}
The new api routes are as follows:
Route::namespace('API\Auth')->group(function () {
Route::post('login', 'PassportController#login');
Route::post('refresh', 'PassportController#refresh');
Route::post('logout', 'PassportController#logout');
Route::get('email/verify/{id}/{hash}', 'VerificationApiController#verify')->name('verification.api.verify');
Route::get('email/resend', 'VerificationApiController#resend')->name('api.verification.resend');
});
And the VerificationApiController is as follows:
<?php
namespace App\Http\Controllers\API\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Auth\Access\AuthorizationException;
class VerificationApiController extends Controller
{
/**
* Show the email verification notice.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function show(Request $request)
{
//
}
/**
* Mark the authenticated user's email address as verified.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function verify(Request $request)
{
if (! hash_equals((string) $request->route('id'), (string) $request->user('api')->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->route('hash'), sha1($request->user('api')->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($request->user('api')->hasVerifiedEmail()) {
return response()->json(['error' => 'Email already verified'], 422);
}
if ($request->user('api')->markEmailAsVerified()) {
event(new Verified($request->user('api')));
}
return response()->json(['success' => 'Email verified!']);
}
/**
* Resend the email verification notification.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function resend(Request $request)
{
$request->user('api')->sendApiEmailVerificationNotification();
return response()->json(['success' => 'Email verification has been resent!']);
}
}
I also noticed that onn the User Model it extends User as Authenticatable which then uses the standard MustVerifyEmail' - so I brought that out of the framework as well and changed the usage to the new MustVerifyApiEmail - like so:
<?php
namespace App\Http\Controllers\API\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use App\Http\Controllers\API\Auth\MustVerifyApiEmail;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyApiEmail;
}
My User model then looks like this at the top:
<?php
namespace App;
use Illuminate\Support\Str;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use App\Http\Controllers\API\Auth\User as Authenticatable;
use App\Http\Controllers\API\Auth\MustVerifyApiEmailInterface;
class User extends Authenticatable implements MustVerifyApiEmailInterface
{
use HasApiTokens, Notifiable;
...
As you can see it's quite a bit of customization - but it should all work in theory and I am getting no errors that I can use. Here are the errors that I get:
When I login with a user who's email is verified or even not verified, I get the error that the user's email has not been verified - but only because it doesn't pick up the $request->user('api'). When I try to throw an error in the middleware itself before returning the request dumping the $request->user('api') it gives me null
So my question is, with the standard middleware of 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - how does this pick up the $request->user()?
Is there something I am missing or am I going about this the wrong way? It seems that when the user logs in it doesn't log them in and then run the middleware - so there is no $request->user('api') - maybe because I am using Passport, but I would think that what should happen is that the middleware needs to run after it has authenticated the user then it would have access to the $request->user('api')
ANY GUIDANCE WOULD BE EXTREMELY APPRECIATED!
Hopefully you have long since solved this one, but since I came across this same problem earlier I thought I'd share my solution. I wanted to use verify on login of the api, but realised that this would be invoked before the user was logged in or authenticated which means there is no user on the request.
In my case I made changes to my custom EnsureEmailsVerified like this:
<?php
namespace App\Http\Middleware;
use App\User;
use Closure;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EnsureApiEmailIsVerified
{
/**
* Handle an incoming request.
*
* #param Request $request
* #param \Closure $next
* #return JsonResponse
*/
public function handle($request, Closure $next)
{
$user = $request->user() ?? User::where('email', $request->get('username'))->first();
if (! $user ||
($user instanceof MustVerifyEmail &&
! $user->hasVerifiedEmail())) {
return response()->json(['error' => [
'message' => __('errors.email_not_verified'),
'status_code' => 401,
]], 401);
}
return $next($request);
}
}
And likewise I made a custom VerifiesEmails trait and changed the verify method to find the user based on the id in the email link because I know this will be going to new, non-logged in users:
public function verify(Request $request)
{
$user = User::find($request->get('id'));
if (! hash_equals((string) $request->get('id'), (string) $user->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->get('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->hasVerifiedEmail()) {
return new Response('', 204);
}
if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
if ($response = $this->verified($request)) {
return $response;
}
return new Response('', 204);
}
I am using laravel 5.5, want to record Last Login (date time)and Last Login IP on each successful login.but its not updating.
LoginController:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected function authenticated(Request $request, $user)
{
$user->update([
'last_login_at' => Carbon::now()->toDateTimeString(),
'last_login_ip' => $request->getClientIp()
]);
}
}
User Model:
protected $fillable = [
'first_name','last_name', 'email', 'password','phone','user_type','last_login_at',
'last_login_ip',
];
Did you imported carbon package?
Anyway to implement this i think https://laravel.com/docs/5.7/events is an elegant way.
Inside app\Providers\EventServiceProvider.php write below code
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
];
Create a Listener Inside app\Listeners\LogSuccessfulLogin.php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
class LogSuccessfulLogin {
/**
* Create the event listener.
*
* #return void
*/
public function __construct(){
}
/**
* Handle the event.
*
* #param Login $event
* #throws \Exception
*/
public function handle(Login $event){
$user = $event->user;
$user->last_login_ip = request()->getClientIp();
$user->save();
}
}
Just update the handle() as you wish. Import carbon package here & update last_login_at.
Recording last login information is useful and you can easily save and update the last login time and IP address of the client.
You can achieve this in several ways but I am gonna show you very simple and effective technique, just add the below-shown method to the login Controller
function authenticated(Request $request, $user)
{
$user->last_login = Carbon::now()->toDateTimeString();
$user->last_login_ip = $request->getClientIp();
$user->save();
}
If you want to record the last visit of the user, firstly run this command
php artisan make:middleware LastVisit
Then add this line to the end of the middlewareGroups array (App\Http\Kernel.php)
\App\Http\Middleware\LastVisit::class,
Lastly change your LastVisit middleware. (I assume that you have a last_visit column in your users table)
<?php
namespace App\Http\Middleware;
use Carbon\Carbon;
use Closure;
use Illuminate\Support\Facades\Auth;
class LastVisit
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!Auth::check()) {
return $next($request);
}
$user = Auth::User();
$user->timestamps = false;
$user->last_visit = Carbon::now()->toDateTimeString();
$user->save();
return $next($request);
}
}
This works with remember me token and social logins too.
PROBLEM
I'm busy with my first Laravel app and although I see the benefits of the way things are written, I'm having a hard time understanding some of the behaviour.
When I try to login I get redirected to the login page. It seems like the user authenticates correctly, but it redirects to the login page regardless.
WHAT I HAVE
My users table looks like this:
,---------,---------------,---------------,----------------,---------------,-----------,
| user_id | user_username | user_password | user_firtsname | user_lastname | user_type |
|---------|---------------|---------------|----------------|---------------|-----------|
| 1 | me#domain.com | encrypted | Foo | Bar | farmer |
'---------'---------------'---------------'----------------'---------------'-----------'
This is my routes file:
<?php
Route::get('/login', 'UsersController#login');
Auth::routes();
Route::get('/dashboard', 'HomeController#dashboard')->name('dashboard');
Route::get('/users/grid', 'UsersController#grid');
Route::resource('/users', 'UsersController');
LoginController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* #var string
*/
protected $redirectTo = '/dashboard';
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* #return string
*/
public function username()
{
return 'user_username';
}
/**
* #param Request $request
* #param $user
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function authenticated(Request $request, $user)
{
return $user->user_type === 'farmer' ? redirect('/dashboard') : redirect('/admin');
}
}
User.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* #var array
*/
protected $fillable = [
'user_username', 'user_firstname', 'user_lastname', 'user_password',
];
/**
* #var array
*/
protected $hidden = [
'user_password', 'remember_token',
];
/**
* #var bool
*/
public $timestamps = false;
/**
* #return mixed|string
*/
public function getAuthPassword()
{
return $this->user_password;
}
/**
* #return string
*/
public function getKey()
{
return $this->user_id;
}
}
WHAT I'VE DONE
I've read various questions on stackoverflow but for some reason I can't get login to work.
I created the auth using php artisan make:auth. I've tried reading the documentation too, but still no luck.
QUESTION
How do I get it to redirect to the dashboard after login? What am I missing?
You need to add this prop in your user model.
protected $primaryKey = 'user_id';
Remove the authenticated() method from login controller.
And handle the redirection based on user type inside RedirectIfAuthenticated middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
if (Auth::guard($guard)->user->user_type === 'farmer'){
redirect('/dashboard'); // make sure the urls is correct with condition above
}
return redirect('/admin');
}
return $next($request);
}
}
Extra step inside App\Exceptions\Handler class
Add this method if you have different login pages for each user type
/**
* #param Request $request
* #param AuthenticationException $exception
* #return JsonResponse|RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
if ($request->is('dashboard') || $request->is('dashboard/*')) {
return redirect()->guest('/dashboard/login');
}
return redirect()->guest('farmer/login');
}
I have had the same problem yesterday night. I have found that the issue is Auth::attempt() does not persist the login so when a user is logged in successfully, it was dropped in the session after redirect to /home.
Below link provided the answer to solve it:
Laravel Auth:attempt() will not persist login
Try to add something like
protected $primaryKey = 'user_id'; in class User{}
(app/models/User.php)
(Field user_id is auto increment key in my Schema for 'users' table)
You are using user_password field instead of password field. So you need to do some changes in LoginController.php file. Below is the updated LoginController. Try to do changes like this and then try to login
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* #var string
*/
protected $redirectTo = '/dashboard';
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* #return string
*/
public function username()
{
return 'user_username';
}
/**
* #param Request $request
* #param $user
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function authenticated(Request $request, $user)
{
return $user->user_type === 'farmer' ? redirect('/dashboard') : redirect('/admin');
}
/**
* Validate the user login request.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => 'required|string',
'user_password' => 'required|string',
]);
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only($this->username(), 'user_password');
}
}
I added two functions validateLogin and credentials where default field was password. and I changed this with user_password.
for authentication in laravel first use php artisan make:auth command
and in your web.php add Auth::routes(); only which have all authentication routes
and to check if you are login or not you should add $this->middleware('auth'); in constructor of controller like
public function __construct()
{
$this->middleware('auth');
}
and do not forget to call auth class like use Auth;
or you can check authentication in your routes by add middleware auth
and also check https://laravel.com/docs/5.7/authentication documentaion
I am trying to create multiple Auth Guards and services for my API. I want specific group to be available to specific users (more like RBAC without sessions).
If a user tries to access a group that has admin:auth as middleware its privileges will be checked. If it has api:auth then no privilege check.
I can't understand how to do this. I have added the following lines in the bootstrap/app.php
$app->routeMiddleware([
'admin' => App\Http\Middleware\Admin::class,
'api' => App\Http\Middleware\Api::class,
]);
And:
$app->register(App\Providers\AdminServiceProvider::class);
$app->register(App\Providers\ApiServiceProvider::class);
and created the Files Admin.php and APi.php in Middleware folder with the following content (basically same as Authenticate.php with name changes)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Admin
{
/**
* The authentication guard factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
And the AdminServiceProvider and ApiServiceProvider in the App\Providers folder with just this in function boot():
var_dump($this->app['api']);exit;
And this on top:
namespace App\Providers;
use App\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class ApiServiceProvider extends ServiceProvider
But I get the Following Error:
What am I missing? I have already done composer dump-autoload, no difference.
Regards
Well what I ended up using is more like a work around, but I think this might be how it is supposed to be used.
So what I did was that I created two groups (a parent and a child) with the middlewares I needed as follows:
$app->group(["middleware" => "middleware1"], function() use ($app){
$app->group(["middleware" => "middleware1"], function() use ($app){
$app->post("/signin", "AppController#signin");
}
}
This way, the signin route is reached after going through 2 middlewares.
Regards