Laravel 5.x - Custom middleware for authenticating a PIN - laravel

I have read the middleware documentation for Laravel yet I feel like im misunderstanding something.
What I want to do is simply to authenticate everytime before being able to view a book.
E.g. If I go to '\book\funnybook' redirect => '\book\funnybook\authenticate' (which says enter a PIN number)
The problem I am having is:
I dont know where to place the redirect function (middleware? controller?)
I dont know where to retrieve the value of the user input and compare it with the database value. Meaning again where should I place it? in the middleware or controller? and how would my view call the function?

In post function of authenticate in Controller.. Similar below
public function postPin(Request $request){
...
...
return Redirect::intended();
}

Let's create a minimal example of such middleware so you'll be able to improve it from there.
From your explanation I assume that every book will have its own pin. You can change that behavior if you want something else.
First, let's create our routes for view/authenticate books:
routes/web.php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'book'], function () {
// view authenticated book
Route::get('{bookSlug}', 'BookController#view')->name('book.view');
// show book authenticate form
Route::get('{bookSlug}/authenticate', 'BookController#showAuth')->name('book.authenticate');
// handle user input, authenticate book
Route::post('{bookSlug}/authenticate', 'BookController#authenticate');
});
Let's also add pattern for bookSlug parameter (update boot method as shown):
app/Providers/RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider {
// ...
public function boot()
{
Route::pattern('bookSlug', '[A-Za-z0-9_-]+');
parent::boot();
}
// ...
}
Now, let's create the middleware:
php artisan make:middleware CheckPin
Every middleware has handle method that allows it to check for something and either allow request to pass next (by calling $next() closure), or stop request processing by throwing an error or redirecting to another URL.
Here, we'll check book authentication status in session. It will be stored there by auth form. If book isn't authenticated yet, we'll redirect to the auth form.
app/Http/Middleware/CheckPin.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Route;
class CheckPin {
public function handle($request, Closure $next)
{
$bookSlug = Route::current()->parameter('bookSlug');
// check whether book has been authenticated
// redirect to auth form otherwise
if(!session("books.$bookSlug")) {
return redirect()->route('book.authenticate', compact('bookSlug'));
}
return $next($request);
}
}
We have to register our middleware in Kernel.php (update it as shown):
app/Http/Kernel.php
use App\Http\Middleware\CheckPin;
//...
class Kernel extends HttpKernel {
//...
protected $routeMiddleware = [
//...
'pin' => CheckPin::class, //
];
protected $middlewarePriority = [
//...
CheckPin::class,
];
//...
}
Let's create our simple views. Create resources/views/book folder and put views there.
View for displaying book content:
resources/views/book/view.blade.php
<h1>View book → {{ $bookSlug }}</h1>
<p>Some content</p>
View for display book auth form:
resources/views/book/auth.blade.php
<h1>Authenticate → {{ $bookSlug }}</h1>
<form method="post" action="{{ route('book.authenticate', compact('bookSlug')) }}">
#csrf
<label for="pin">Enter pin:</label>
<input type="password" name="pin" id="pin" required autofocus />
<button class="submit">Authenticate</button>
#error('pin')
<p>
<i><b>Error:</b> {{ $message }}</i>
</p>
#enderror
</form>
And finally, let's create book controller that will show books, auth form, and handle user input.
php artisan make:controller BookController
app/Http/Controllers/BookController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BookController extends Controller {
public function __construct()
{
// "pin" middleware will protect all methods except #showAuth and #authenticate
$this->middleware('pin')->except(['showAuth', 'authenticate']);
}
function view(Request $request, string $bookSlug)
{
return view('book.view', compact('bookSlug'));
}
function showAuth(Request $request, string $bookSlug)
{
return view('book.auth', compact('bookSlug'));
}
// handles user input, checks for valid pin
// redirects to the authenticated book or back to the form
function authenticate(Request $request, string $bookSlug)
{
$pin = $request->input('pin');
// Check book pin here; for testing purpose: pin should equal book slug
if($pin === $bookSlug) {
session()->put("books.$bookSlug", 1);
return redirect()->route('book.view', compact('bookSlug'));
}
return redirect()->back()->withErrors(['pin' => ['Invalid pin']]);
}
}
It works like this:
When user opens some book, for example book/funny it will be redirected to book/funny/authenticate where they can enter pin and authenticate the book. If the pin is valid, then user is redirected back to book/funny URL, otherwise the error is shown.
That's all. You can extend this example to better suit your needs: add more methods to BookController, check book pin from database etc.

Related

Same route return different view according to user role laravel 9

I have a column on my user table called role, and it can have 2 different values, "employer" and "employee".
What I want to achieve is, on /dashboard url;
if not logged in : return welcome view
if logged in as employer : return employer.dashboard view
if logged in as employee : return employee.dashboard view
Problem with this code is, I'm logging in with my employer user, and when I go to /dashboard, it sends me welcome view even though I'm logged in. It works fine with employee user. However when I switch places of two routes, Employer page works but Employee breaks. ( redirects to / ) It always uses last declared route instead of following the middleware.
My routing:
Route::prefix('admin')->middleware('role:employer')->group(function() {
Route::view('/dashboard', 'employer.dashboard')->name("dashboard");
});
Route::prefix('store')->middleware('role:employee')->group(function() {
Route::view('/dashboard', 'employee.dashboard')->name("dashboard");
});
Role middleware :
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class HasRole
{
public function handle(Request $request, Closure $next, string $role)
{
if (Auth::user()?->role != $role) {
return redirect()->route('welcome');
}
return $next($request);
}
}
I'd suggest moving the routing logic to a closure or controller, which would be much easier.
Route::get('/dashboard', function () {
if (auth()->user()->role === 'employer') {
return view('employer.dashboard');
} else {
return view('employee.dashboard');
}
})->name('dashboard');
And then for the middleware, you can just update the already available middleware RedirectIfAuthenticated to route the user to the home page if not logged in. Or just update RouteServiceProvider::HOME.

How to pass Eloquent Data to blade component?

Hello,
I have a blade Layout called:
profile.blade.php // component
And I have a blade file infos which extends from profile.blade.php component.
On my controller, I have a profile method:
public method profile(User $user) {
return view('infos.blade.php', compact('user'));
}
When I try to use the variable user to the profile.blade.php component, I have an error that says "undefined user variable"
My question is how can I get the data I received from my controller to Blade Component profle ?
Because that part of component will be used many times.
Thanks.
in your app/view/components/profile-
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class profile extends Component
{
public $user; //for consistency
public function __construct($user)
{
$this->user = $user;
}
public function render()
{
return view('components.profile');
}
}
now the component can render a variable ($user),
in your info's blade/infos.blade.php you can feed the
variable to the component like --
<x-profile :user='$user' ></x-profile>
i understand the question was posted quite a long ago
but i had the same issue and the answers posted here
didn't work for me but this method did so.....it may help somebody.
As it's a component you have to pass user parameter each time you call it, for example
#component('profile', ['user' => $user]) #endcomponent // here $user comes from controller

Laravel: How to make sure a user goes to role-specific home view after registration and after login?

I have created a custom multi-auth system in laravel-5.8 for a college website where every user registers for a specific role: Admin, Faculty, or HOD(Head of Department).
I am making use of the controllers and views which we get after running
php artisan make:auth
Upon registering, all the users are redirecting to default home page, regardless of their roles. The 3 roles are being stored in the Users table as 0, 1, and 2 under column role. I modified the Users table which comes by default.
My login mechanism works well. Users are redirected to the home page of their role. Here is how I do it:
LoginController.php
public function login(Request $request){
if(Auth::attempt(['PID' => $request->PID, 'password' => $request->password])){
$user = User::where('PID',$request->PID)->first();
if($user->role == '0'){
return redirect('home');
}
else{
if($user->role == '1'){
return redirect('hodhome');
}
else{
return redirect('adminhome');
}
}
}
return redirect()->back();
}
This is the route used upon the POST request from login.blade.php
Route::post('/login/custom', 'auth\LoginController#login')->name('login.custom');
But every time I register a new user, it takes users to the faculty home page route, even when they're admins or HODs. Here's the register mechanism:
RegisterController.php (Just showing relevant functions below)
use RegistersUsers;
protected $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest');
}
//validator function and create function after this line
I have tried to make it work properly by doing this: Changing the variable redirectTo to a function.
//an idea: replace "protected $redirectTo = '/home';" by...
protected function redirectTo(array $data){
if($data['role']=='0'){ return redirect('home'); }
elseif($data['role']=='1'){ return redirect('hodhome'); }
else return redirect('adminhome');
}
But it doesn't work, because I seem to be missing something.
Another bummer is the fact that after login, users can access home pages of other roles. the middleware('auth') in my routes helps protect against accessing the URLs when not logged in, but it doesn't stop logged in users from accessing other home pages.
Here are the routes to the home pages:
Auth::routes();
//-------------------MULTI AUTH SYSTEM------------------------------------------
Route::post('/login/custom', 'auth\LoginController#login')->name('login.custom');
Route::get('/home', 'HomeController#index')->name('home');
route::get('/adminhome', function () {
return view('adminhome');
})->middleware('auth');
route::get('/hodhome', function () {
return view('hodhome');
})->middleware('auth');
//----------------------------------------------------------------------------
The default welcome.blade.php too redirects logged in users to faculty's home page:
#if (Route::has('login'))
<div class="top-right links">
#auth
Home
#else
Login
#if (Route::has('register'))
Register
#endif
#endauth
</div>
#endif
How do I make sure users go to the right home pages upon a new registration and when logging in?
How do I make sure home page of one role stays inaccessible to other roles?
EDIT 1:
After incorporating changes suggested by #N69S, This is what's happening:
pages have become role specific.
one role can not access pages of another role, and unauthenticated users can not view any pages except the welcome page.
That solution worked. But when an illegal access to a URL is attempted, an error has to be generated.
But instead, user is being sent to the welcome page. This page has only one link in the top right corner, called "HOME". This link does not work when either admin or HOD user is logged in because it routes to '/home' as shown:
//welcome.blade.php
<body>
<div class="flex-center position-ref full-height">
#if (Route::has('login'))
<div class="top-right links">
#auth
Home
#else
Login
#if (Route::has('register'))
Register
#endif
#endauth
</div>
#endif
<div class="content">
<div class="title m-b-md text-center">
COLLEGE ADMINISTRATION <br> SYSTEM
</div>
</div>
</div>
</body>
How do I change this view file to send logged in user to his role's HOME?
One complete and correct solution would be to separate the entities (HOD, Admin, User) and configure the auth for each one (auth:hod, auth:admin, auth)
Another solution would be to make 3 separate middleware and group your routes with one of them accordingly.
btw, redirectTo() method does not take parameters. From Illuminate\Foundation\Auth\RedirectsUsers trait
/**
* Get the post register / login redirect path.
*
* #return string
*/
public function redirectPath()
{
if (method_exists($this, 'redirectTo')) {
return $this->redirectTo();
}
return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
}
---------------Edit
For the 3 middleware solution, you can use the command php artisan make:middleware Admin, this will create the Class file app/Http/ Middleware/Admin.php.
in the handle method, you can do something like
public function handle($request, Closure $next)
{
if(auth()->user() && auth()->user()->role == 2){
return $next($request);
}
return redirect(‘login’)->with(‘error’,’You have not admin access’);
}
and do the same for the HOD
To use it like authin the routes, add the middleware in the kernel App\Http\Kernel.php in $routeMiddleware
protected $routeMiddleware = [
//....,
//....,
'admin' => \App\Http\Middleware\Admin::class,
];

Cant logout from custom Admin Panel Laravel 5.7

I liked Matrix Admin panel, so i started implementing it. I didnt like the option of having admin in User model (boolean check isAdmin), so i created a separate model called Admin. I set up all the routes, and it worked in my test admin page with the following routes:
web.php
Route::get('/admin/login', 'Auth\AdminLoginController#showLoginForm')-
>name('admin.login');
Route::post('/admin/login', 'Auth\AdminLoginController#login')-
>name('admin.login.submit');
Route::get('/admin', 'AdminController#index')->name('admin.admin');
This is my AdminLoginController:
public function showLoginForm()
{
return view('admin/admin-login');
}
protected function guard(){
return Auth::guard('admin');
}
use AuthenticatesUsers;
protected $redirectTo = '/admin/login';
public function __construct()
{
$this->middleware('guest:admin')->except('logout');
}
And my AdminController:
public function __construct()
{
$this->middleware('auth:admin');
}
public function index()
{
return view('admin.admin');
}
I call routes in the view like this:
href="{{ route('admin.login') }}"
For some reason, it doesn't log me out and get me to the login page, instead it just refreshes the page, hence returns me to the /admin page with the admin logged in. What am i missing here? Thanks for all replies.
In the constructor for the AdminLoginController, you have set the middleware to be 'guest:admin'.
This uses the guest middleware that is defined in /app/Http/Middleware/RedirectIfAuthenticated.php by default, or configured in /app/Http/Kernel.php
The $guard string passed for it to use is 'admin'.
I'm guessing that you provide an implementation for this admin guard.
Supposing that you're logged in as an admin, navigating to route('admin.login')
will redirect your request to '/home' in the default implementation;
there are likely chances that there exists a custom redirection to /admin in your implementation.
If you like to logout of the session, you can register a route for the logout method provided in Illuminate\Foundation\Auth\AuthenticatesUsers trait.
Route::get('/admin/logout', 'AdminController#logout')->name('admin.logout');
Since the AuthenticatesUsers::logout redirects to / by default, I'm guessing you need to override this behavior.
You can do exactly that by providing a custom implementation for AuthenticatesUsers::loggedOut in your controller.
protected function loggedOut(Request $request)
{
return redirect('/admin');
}
Finally, provide this route for the href in your link {{ route('admin.logout') }}
It's all about session,
\Auth::logout();
$request->session()->invalidate();

Laravel 5 customizing built in login redirect

I am customizing laravel 5's built in login so that it would redirect to three different paths according to the type column which i added to the users table, i tried altering the handle function of RedirectIfAuthenticated middleware. but it seems that it always finds the home URI.
here is my edited middleware
public function handle($request, Closure $next)
{
if ($this->auth->check() && $this->auth->user()->type == 'patient') {
// return redirect('/home');
return 'PATIENT VIEW';
} elseif ($this->auth->check() && $this->auth->user()->type == 'doctor') {
return 'DOCTOR VIEW';
} elseif ($this->auth->check() && $this->auth->user()->type == 'nurse') {
return 'NURSE VIEW';
}
return $next($request);
}
Im new to laravel 5 and i would really appreciate any help and explanations
RedirectIfAuthenticated is being misused here. That middleware is for when an authenticated user tries to access a page that should only be accessed by guests. For example, if I am a user and I try to view the login or registration forms, it doesn't let me.
I would not mess with the authentication itself... some of it is easily customizable but what you're trying to do is not. I would just let Laravel authenticate them first and then handle what to do after.
/home is the default route users are taken to when they login. Move your if checks to that route controller method. Better yet... if you set things up right you don't need any checks at all.
class HomeController {
public function index()
{
$user = \Auth::user();
return view($user->type . '.dashboard');
}
}
Now you just need views named patient/dashboard.blade.php, doctor/dashboard.blade.php, etc. If you have more complex logic then you might want an actual redirect
return redirect('home/' . $user->type);
Define routes for each of those types
Route::get('home/patient', 'PatientController#dashboard');
Route::get('home/doctor', 'DoctorController#dashboard');
Route::get('home/nurse', 'NurseController#dashboard');
And then do whatever you need to in those controller methods.
Check out the docs in Authentication section
Basically what you need is:
Create the auth routes at app/Http/routes.php:
// Authentication routes...
Route::get('auth/login', 'Auth\AuthController#getLogin');
Route::post('auth/login', 'Auth\AuthController#postLogin');
Create the login form view:
<!-- resources/views/auth/login.blade.php -->
<form method="POST" action="/auth/login">
{!! csrf_field() !!}
<div>
Email
<input type="email" name="email" value="{{ old('email') }}">
</div>
<div>
Password
<input type="password" name="password" id="password">
</div>
<div>
<input type="checkbox" name="remember"> Remember Me
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
Manually Authenticate users app/Http/Controllers/Auth/AuthController.php:
<?php
namespace App\Http\Controllers;
use Auth;
use Illuminate\Routing\Controller;
class AuthController extends Controller
{
/**
* Handle an authentication attempt.
*
* #return Response
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
if (Auth::user()->type == 'patient'){
return redirect()->intended('patientDashboard');
}
if (Auth::user()->type == 'doctor'){
return redirect()->intended('doctorDashboard');
}
}
}
}
Or if you want to keep the logic under RedirectIfAuthenticated middleware you could just fix your code:
public function handle($request, Closure $next)
{
if ($this->auth->check())
{
//we have a logged user check if it's patient
if($this->auth->user()->type == 'patient'){
return new RedirectResponse(url('/patient'));
}else if($this->auth->user()->type == 'doctor'){
return new RedirectResponse(url('/doctor'));
}
}
return $next($request);
}
Also you should check out this Entrust package.

Resources