Apply Laravel 5.7 MustVerifyEmail on Multiple Authentication System - laravel

I'm trying to apply Laravel-5.7 MustVerifyEmail on multiple authentication system. So far what I've done is as follows:
created verification routes for the 'auditor' guard.
overwrite the show method in Verification controller with a new view.
Implemented a new notification in Auditor Model.
Created, register and applied a new middleware called 'auditor.verified'
After this procedure, I find that it's sending a notification to email and shows the verify page but when I click on the 'Verify Email Address' button in the mail it update the database with the timestamp but it don't take me to the redirect page. Instead, I get "The page isn't working" message in the browser.
There should be something I missed.
Here is the project file on GitHub
Thanks in advance for your help.

M.Islam's answer is a good one, but make sure to override the changes to EnsureEmailIsVerified instead of directly modified the source files. Otherwise your changes could be lost whenever you do $composer update or push to production.

Finally, after four days of research I was able to solve the issue.
I altered the "EnsureEmailIsVerified" middleware as follows:
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Auth;
class EnsureEmailIsVerified
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle($request, Closure $next, $guard = null)
{
$guards = array_keys(config('auth.guards'));
foreach($guards as $guard) {
if ($guard == 'admin') {
if (Auth::guard($guard)->check()) {
if (! Auth::guard($guard)->user() ||
(Auth::guard($guard)->user() instanceof MustVerifyEmail &&
! Auth::guard($guard)->user()->hasVerifiedEmail())) {
return $request->expectsJson()
? abort(403, 'Your email address is not verified.')
: Redirect::route('admin.verification.notice');
}
}
}
elseif ($guard == 'auditor') {
if (Auth::guard($guard)->check()) {
if (! Auth::guard($guard)->user() ||
(Auth::guard($guard)->user() instanceof MustVerifyEmail &&
! Auth::guard($guard)->user()->hasVerifiedEmail())) {
return $request->expectsJson()
? abort(403, 'Your email address is not verified.')
: Redirect::route('auditor.verification.notice');
}
}
}
elseif ($guard == 'web') {
if (Auth::guard($guard)->check()) {
if (! Auth::guard($guard)->user() ||
(Auth::guard($guard)->user() instanceof MustVerifyEmail &&
! Auth::guard($guard)->user()->hasVerifiedEmail())) {
return $request->expectsJson()
? abort(403, 'Your email address is not verified.')
: Redirect::route('verification.notice');
}
}
}
}
return $next($request);
}
}
And that's solved my problem.

So There was a similar question...
StackOverflow::Route [user.verification.notice] not defined / Override EnsureEmailIsVerified?
when using multiple guards you can just some guard redirection in the
App\Middleware\Authenticate.php
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
if (Arr::first($this->guards) === 'admin') {
return route('admin.login');
}
if (Arr::first($this->guards) === 'user') {
return route('user.login');
}
return route('login');
}
}
you can just add all the verify routes to your web.php file and change the named routes.
All auth routes can be found in
Illuminate\Routing\Router.php
\
/**
* Register the typical authentication routes for an application.
*
* #param array $options
* #return void
*/
public function auth(array $options = [])
{
// Authentication Routes...
$this->get('login', 'Auth\LoginController#showLoginForm')->name('login');
$this->post('login', 'Auth\LoginController#login');
$this->post('logout', 'Auth\LoginController#logout')->name('logout');
// Registration Routes...
if ($options['register'] ?? true) {
$this->get('register', 'Auth\RegisterController#showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController#register');
}
// Password Reset Routes...
if ($options['reset'] ?? true) {
$this->resetPassword();
}
// Email Verification Routes...
if ($options['verify'] ?? false) {
$this->emailVerification();
}
}
/**
* Register the typical reset password routes for an application.
*
* #return void
*/
public function resetPassword()
{
$this->get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController#reset')->name('password.update');
}
/**
* Register the typical email verification routes for an application.
*
* #return void
*/
public function emailVerification()
{
$this->get('email/verify', 'Auth\VerificationController#show')->name('verification.notice');
$this->get('email/verify/{id}/{hash}', 'Auth\VerificationController#verify')->name('verification.verify');
$this->post('email/resend', 'Auth\VerificationController#resend')->name('verification.resend');
}
So instead of using the Auth::routes() you would manually add these to your web.php routes file and just give them named routes.
Note: Once you change the named routes you will need to correctly reference them in your views.
The first thing it will complain about is the notifications mail which is referencing the default named routes...
You can over ride this in both the verification mail process and forgot password password by following the example here.
Forgot Password Custom Named Route and Email
To achieve this you would have to override the email notifications by creating two custom ones that override the two default ones.
You would emulate the laravel default structure found in the files
Illuminate\Auth\Notifications\VerifyEmail.php
Illuminate\Auth\Notifications\ResetPassword
Once you have created 2 notifications mailers.
eg
php artisan make:notification MailEmailVerificationNotification
which creates a file in App\Notifications\MailEmailVerificationNotification which effectively replicates the Illuminate\Auth\Notifications\VerifyEmail.php file
You will add the method to your model. Laravel default is User but if you are using custom guards with multiple tenant auth you would apply this to your relevant model.
You will then have the following on you model
/**
* Send the password reset notification.
* App\Notifications\MailResetPasswordNotification.php
*
* #param string $token
* #return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new MailEmailVerificationNotification());
}
Going this route is better because you override the Laravel default logic but you don't edit any Laravel specific files which means they won't get overwritten when updating Laravel and will only be affected when the is design changes like the recent move to extract the Laravel UI into its own package which changed things slightly on the passwords reset route.
you may note that we altered the App\Middleware\Authenticate file... this file is not part of vendor files and while provided to you as part of base install its left for you to alter update and change... the change we made was only to accommodate guards and not extensive change which allows for the multitenancy or not in the app.
For anyone I hope this helps and I went on a journey learning this and hope to reference this when I forget and hope it helps anyone walking a similar path.

I modified the middleware parameter in the __construct and email verification worked for me. I'm using laravel 6. posting the answer here though the question is old
public function __construct()
{
$this->middleware('auth:<your_guard>');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}

Related

Laravel Lighthouse Graphql multiple guards

I am trying to make my custom guard to work against a GraphQL schema with Lighthouse but its not working.
The problem is that my custom guard is not reached by graphql query.
The schema.graphql: (removed some code for brevity)
extend type Query #guard(with: ["api", "partner"])
{
GetHighscores(): [Highscore!]!
}
Note the #guard directive, it has api and partner. I want they run in that order (btw does it have order?).
Now the definition of the partner guard
AuthServiceProvider#boot
Config::set('auth.guards.partner', [
'driver' => 'partner',
]);
Auth::viaRequest('partner', function (Request $request) {
dd('MUST GET HERE');
});
When I run the query using an authenticated bearer token, the guard partner is not executed. But if I set the partner guard before api then it gets executed. What am I missing here?
edit
Using CURL, I have requested a protected laravel route and it works:
Route::middleware(['auth:api', 'auth:partner'])->get('/partners', function (Request $request) {
print_r($request->user()->toArray());
});
it printed the dd() message, which is right.
So why Lighthouse could not reached my custom guard?
I solved my issue moving the check for the API guard inside the partner guard:
Auth::viaRequest('partner', function (Request $request) {
$user = auth()->user();
if ($user &&
$user->partner()->exists() === true &&
auth()->guard('api')->check() === true
) {
return $user;
}
});
in the graphql schema:
extend type Query #guard(with: "partner")
{
GetHighscores(): [Highscore!]!
}
I still thinking that Lighthouse should be take care of multiple guards like it was an "AND"... anyways, its working now.
The lighthouse #guard directive behaves like a OR directive.
If the first guard passes, the second one is never checked.
What you could to is add the #guard directive twice. I think that should work.
extend type Query #guard(with: ["api"]) #guard(with: ["partner"])
{
GetHighscores(): [Highscore!]!
}
This is the code from the directive:
/**
* Determine if the user is logged in to any of the given guards.
*
* #param array<string> $guards
*
* #throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards): void
{
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
$this->auth->shouldUse($guard);
return;
}
}
$this->unauthenticated($guards);
}
My use case may be different as I wanted authorization not multiple kinds of authentication, I have users that should be allowed to authenticate but need to do some verification mutations and limited querying and be admitted to use the rest of the API by an administrator.
Was thinking to just copy the guard directive and make it work as an and?
But I ended up creating a FieldMiddleware directive as I did not want other checks to need to do a separate authentication or copy same auth guard everywhere as that could be subject to change, I just wanted to check some attributes from the model were okay for a user to continue to use that query or mutation.
Although it does seem like #can and policy or gate would be more appropriate if not authenticating but checking some role/permission/state, I just wanted something I could use that was clear cut as I haven't made policy for absolutely every model yet a lot of them are just querying any data, using Policy would also apply for my back-end system which has different checks for admin already.
sail artisan lighthouse:directive AllowedInApp --field
1 FieldMiddleware
So the field Middleware can wrap and do checks I need and throw if not allowed.
<?php
namespace App\GraphQL\Directives;
use App\Models\User;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Exceptions\AuthorizationException;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
final class AllowedInAppDirective extends BaseDirective implements FieldMiddleware
{
public static function definition(): string
{
return /** #lang GraphQL */ <<<'GRAPHQL'
directive #allowedInApp on FIELD_DEFINITION
GRAPHQL;
}
/**
* Wrap around the final field resolver.
*
* #param \Nuwave\Lighthouse\Schema\Values\FieldValue $fieldValue
* #param \Closure $next
* #return \Nuwave\Lighthouse\Schema\Values\FieldValue
*/
public function handleField(FieldValue $fieldValue, Closure $next)
{
$previousResolver = $fieldValue->getResolver();
$fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($previousResolver) {
/***
* #var User
*/
$user = $context->user();
if ($user->isVerifiedAndAdmitted === false) {
throw new AuthorizationException(
"You are not authorized to access {$this->nodeName()}"
);
}
return $previousResolver($root, $args, $context, $resolveInfo);
});
return $next($fieldValue);
}
}
In my user model I added an attribute that did the check I wanted, only have users but it is possible to have multiple kinds of authenticatable model actually.
/**
* Get verified status returns true if both mobile and email are verified and the user is admitted.
*
* #return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function isVerifiedAndAdmitted(): Attribute
{
return Attribute::make(
get: fn($value, $attributes) => isset($attributes['mobile_verified_at'])
&& isset($attributes['email_verified_at'])
&& $attributes['admitted_at'] !== null
&& now()->greaterThanOrEqualTo($attributes['admitted_at'])
);
}
From what I saw about the guard directive in lighthouse source.
The built in Guard Directive just returns when one defined guard or default guard succeeds.
src/Auth/GuardDirective.php
Untested, not sure its the right thing to use, maybe policy or gate is better.
/**
* Determine if the user is logged in to all of the given guards.
*
* #param array<string|null> $guards
*
* #throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards): void
{
$canPass = true;
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
// #phpstan-ignore-next-line passing null works fine here
$this->auth->shouldUse($guard);
} else {
$canPass = false;
break;
}
}
if ($canPass) {
return;
}
$this->unauthenticated($guards);
}
```

Custom Guard Authentication Exception

I created a custom guard (admin) in my application, and everything looks good following this tutorial online (https://pusher.com/tutorials/multiple-authentication-guards-laravel). The only problem is when i try to access a view that is protected by the admin guard, instead of giving me a exception NotAuthenticate, is giving me a "InvalidArgumentException
Route [login] not defined.".
My Dashboard controller custom guard is:
public function __construct()
{
$this->middleware('auth:admin');
}
But the strange thing that i cant understand whats going on is that when i add in my web.php routes the "Auth::routes();", it works fine, the Exception NotAuthenticate gets fired.
Is there a reason why my custom only works has expected with i add the Auth::routes() ?
Admin Login Controller:
namespace App\Http\Controllers\Admin\Auth;
use Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/admin/dashboard';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest:admin')->except('logout');
}
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
public function showLoginForm()
{
return view('admin.auth.login');
}
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return $this->loggedOut($request) ?: redirect('/admin');
}
/**
* Get the guard to be used during authentication.
*
* #return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('admin');
}
}
Try following the following tutorial, it did the trick for me.
On a side note: Avoid modifying the existing controller that laravel ships with, instead extend it and implement your own functionality. This practice will take you the long way.
https://www.codementor.io/okoroaforchukwuemeka/9-tips-to-set-up-multiple-authentication-in-laravel-ak3gtwjvt

laravel 5.5. how to response a redirect from a action, not the route action

I want to check the auth before run the action. if the user deny, I will response the 403 status.
I use the method function in the __construct to check .
the code is following.
The User Controller:
public function __construct()
{
if (!app()->runningInConsole()) {
$beforeMethod = \Route::getCurrentRoute()->getActionMethod()."before";
if (method_exists($this, $beforeMethod)) {
return $this->$beforeMethod();
}
}
}
/**
* Edit interface.
*
* #param $id
* #return Content
*/
public function edit($id)
{
return "success";
}
/**
* Check user Auth
*
* #return \Illuminate\Http\RedirectResponse
*/
public function editBefore()
{
$id = \request()->route('user');
if (Gate::denies('edit', User::find($id))) {
return redirect("login");
}
}
the above code, don't return to the login.
what code should I use to achieve my purpose? Thanks!
You're really close to what I would do. I would use Policies.
You can add these to the routes or route groups so that you can check the policy before it hits the controller method whatsoever.
Create a new policy class that looks something like:
class UserPolicy
{
public function canEdit(User $user) // this user is the logged in user
{
// Here you return true or false depending on whether they can edit or not.
return $user->isAllowedToEdit();
}
}
Then you make sure the policy is added to AuthServiceProvider.php:
public function boot(GateContract $gate)
{
$gate->define('user.edit', UserPolicy::class.'#canEdit');
// Additional policies
}
Then make sure to add can to your $routeMiddleware in the Kernel.php:
protected $routeMiddleware = [
// other middleware
'can' => \Illuminate\Auth\Middleware\Authorize::class,
];
Finally, you can add it to your route making sure that you use the same name you defined in the AuthServiceProvider:
Route::get('/user/edit')
->uses('UserController#edit')
->middleware('can:user.edit');
I personally like doing it this way because it cleans up the controllers and lets the policies do all the work "behind the scenes". Even better, you can then use that same users.edit policy for multiple routes.

Auth and user management in laravel rest api

I'm writing a rest API for a mobile app. I don't know how to auth users and admins in my app.
I have a table named "users" and have a field called "isAdmin" that is 0 or 1.
now when admin sends posts, users can see posts.how do you recommend auth for both of these?
thank you
I recommend you read the documentation about authentication on laravel: https://laravel.com/docs/5.5/authentication
What you have to setup is the following:
Middleware (what routes can the user use and what routes can the admin use)
Edit your model with an isAdmin() function to determine if an user is user or admin
Example of a AdminMiddleware file - create by command line: php artisan make:middleware AdminMiddleware
<?php
namespace App\Http\Middleware;
use Closure;
use Auth;
class AdminMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check() && Auth::user()->isAdmin()){
return $next($request);
}
else{
return view('your_view')->withErrors('You are not logged in');
}
}
}
Example of an User Model isAdmin function - create by command line: php artisan make:model User
public function isAdmin(){
if($this->isAdmin == 1){
return true;
} else {
return false;
}
}
Example of your route file
// #TODO: Set routes for user and admin here...
Route::group(['middleware' => ['admin']], function () {
// #TODO: Set admin routes here, only admin can use this routes.
});
You also have to edit your Kernel.php a bit:
protected $routeMiddleware = [
// ... add this line
'admin' => \App\Http\Middleware\AdminMiddleware::class,
];

Sharing same route with unauthenticated and authenticated users in Laravel 5.3

I have a route that where I'm using an auth middleware and it works great.
Route::group(['prefix' => 'v1','middleware' => ['auth:api']], function()
{
Route::resource('user', 'v1\MyController');
});
The problem is that I would also like this route to be accessible to non-authenticated users as well. With the above, I get a 401 Unauthorized error and I can't return any content for unauthenticated users. So how can I authenticate this route (so it passes down the user data) while also allowing the route to proceed even if the user is NOT authenticated?
(I tried doing a conditional Auth check on the router page but it seems the user has gone through authentication yet so it always remains false.)
EDIT: I should also note that I'm using an API route with Password Grant & access tokens.
remove this route from current route group (which applies auth middleware).
then
public function __construct()
{
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) {
$this->middleware('auth:api');
}
}
then
if (Auth::check()) {
// Auth users
} else{
//Guest users
}
I am experiencing the same case.
since the auth middleware only checks for authenticated user, we can use client credentials for the non-authenticated user.
the client credentials have a separated middleware located in Laravel\Passport\Http\Middleware\CheckClientCredentails.
I have created a custom middleware to combine both middleware to allow either one is pass.
here is my custom middleware
namespace Laravel\Passport\Http\Middleware;
use Closure;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class CheckClientCredentials
{
/**
* The Resource Server instance.
*
* #var ResourceServer
*/
private $server;
/**
* Create a new middleware instance.
*
* #param ResourceServer $server
* #return void
*/
public function __construct(ResourceServer $server)
{
$this->server = $server;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*
* #throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$scopes)
{
$psr = (new DiactorosFactory)->createRequest($request);
try{
$psr = $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
foreach ($scopes as $scope) {
if (!in_array($scope,$psr->getAttribute('oauth_scopes'))) {
throw new AuthenticationException;
}
}
return $next($request);
}
}
Kernal.php
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.api' => \App\Http\Middleware\APIAuthenticate::class,
....
routes\api.php
Route::group([
'namespace' => 'API',
'middleware' => 'auth.api:api',
], function(){
....
From within an unauthenticated (not assigned the auth:api middleware) route's handler method, try:
Auth::guard("api")->user();
If it's populated, then your unguarded route can treat the access as authenticated. If not, its a random user accessing the route, and can be treated as such.
Dont put those urls whome you want to allow for both guest users and authenticated users in auth middleware. Because auth middleware allow for only authenticated users.
To check for authenticated and unauthenticated user you can use following code in view
#if (Auth::guest())
//For guest users
#else
//for authenticated users
#endif
Edited : In controller use
if (Auth::check()) {
// Auth users
} else{
//Guest users
}
#Yves 's answer is nearly correct. But a small change,
Instead of array_key_exists, we need to check whether key value is not null. Because It always has that key but null value. SO, instead of controller construct check for authorization header like this.
if ($_SERVER['HTTP_AUTHORIZATION']) {
$this->middleware('auth:sanctum');
}
Then you can check for authenticated user like this:
if (auth()->check()) {
// User is logged in and you can access using
// auth()->user()
} else {
// Unauthenticated
}
for sanctum use:
if (Auth::guard('sanctum')->check()) {
// user is logged in
/** #var User $user */ $user = Auth::guard('sanctum')->user();
} else {
// user is not logged in (no auth or invalid token)
}
You can use this example:
#if(Auth::user())
echo "authincatesd user";
#else
echo "unauthorised";
#endif

Resources