I'm building some API endpoints with Laravel, and I'm using JWTAuth as the token provider for authorizing requests.
I've gotten the setup to protect a group of API routes that works correctly using:
Route::group(['prefix' => '/v1', 'middleware' => 'jwt.auth'], function() {
Route::resource('messages', 'MessagesController');
});
The Message model belongs to a User
I'm attempting to perform some requests using that relationship while keeping the request from providing data that didn't belong to the user:
Get a list of the logged in user's messages
Get an individual message of the logged in user
The main question I have is how to prevent the user from accessing a Message that does not belong to them. I have this in my controller:
public function show($message_id)
{
$message = Message::findOrFail($message_id);
return $message;
}
That obviously will return the Message regardless of whether or not it belongs to that user. Any suggestions on how to improve that to restrict accessing other user's messages?
For the listing of all messages, I was able to do the following in the controller:
public function index()
{
$user_id = Auth::User()->id;
$messages = Message::where('user_id', $user_id)->paginate(10);
return $messages;
}
This works, but I'm not sure this is the best way to do it. Maybe it is, but some feedback would be appreciated. I'm confused as to whether or not the middleware should be handling what the user has access to or if it should be part of the eloquent query?
Your question is,
how to prevent the user from accessing a Message that does not belong
to them.
Thats falls under Authorization
You can use Gates to Authorize users and I think it's the best approach here. Fist of all you'd need a Policy object.
<?php
namespace App\Policies;
use App\User;
use App\Message;
class MessagePolicy
{
/**
* Determine if the given Message can be viewed by the user.
*
* #param \App\User $user
* #param \App\Message $Message
* #return bool
*/
public function view(User $user, Message $Message)
{
return $user->id === $Message->user_id;
// Here this User's id should match with the user_id of the Massage
}
}
You can even generate the boilerplate like this,
php artisan make:policy MessagePolicy --model=Message
Then you can register it in your AuthServiceProvider like this,
<?php
namespace App\Providers;
use App\Message;
use App\Policies\MessagePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Message::class => MessagePolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('view-message', 'MessagePolicy#view');
}
}
And in your Controller you can use it like this,
public function show($message_id)
{
if (Gate::allows('view-message', Message::findOrFail($message_id)) {
return $message;
}
}
Note: Please use this code snippet as a reference or a starting point since I haven't tested it properly. But underlying concept is correct. Use this as pseudocode. If something wrong found, please update it here :)
Related
Whenever I create a "user", I have to create a line in different tables (like account).
I know that in the controller I can create the user and account like this:
$user = User::create($user_inputs);
$account = $user->account()->create($account_inputs);
$OtherTables...
Is there a way to do this in the model? Always when someone creates a user from another controller, will the lines be automatically inserted in the other tables. Or is it always necessary to indicate it in the controller every time?
You can use Laravel observer
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
/**
* Handle the user "created" event.
*
* #param \App\User $user
* #return void
*/
public function creating(User $user)
{
$user->account()->create([
// your data
]);
}
}
You can use model events for this. https://laravel.com/docs/9.x/eloquent#events-using-closures
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
// This code will be called every time a new user is inserted into the system
static::created(function ($user) {
$user->account()->create([ 'name' => $user->name ])
});
}
}
There are few more events you can use within booted method, the name tells clearly what they do.
creating
created
updating
updated
saving
saved
deleting
deleted
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);
}
```
I'm trying to make this policy stuff work, and i have followed the documentation. But it doesn't seem like the policy code is not even run.
I have Role model. And i created RolePolicy. The thing i want to do in the policy is to ensure that the role with the ID of 1 never-ever gets updated or deleted.
My RolePolicy looks like this:
<?php
namespace App\Policies;
use App\Models\Role;
use Illuminate\Support\Facades\Response;
class RolePolicy
{
/**
* Determine whether the user can update the model.
*
* #param \App\User $user
* #param \App\Models\Role $role
* #return mixed
*/
public function update(Role $role)
{
return $role->id === 1
? Response::deny('Cannot change super-admin role')
: Response::allow();
}
/**
* Determine whether the user can delete the model.
*
* #param \App\User $user
* #param \App\Models\Role $role
* #return mixed
*/
public function delete(Role $role)
{
return $role->id === 1
? Response::deny('Cannot delete super-admin role')
: Response::allow();
}
}
I even tried to do a dd() inside both delete and update method in the policy, but when i try to delete/update the model with the ID of 1, nothing happens. The dd wont run, nor will the response in the current code above.
I have registered the policy in the AuthServiceProvider where i also have this gate to give the super-admin all the permissions.
<?php
namespace App\Providers;
use App\Models\Role;
use App\Policies\RolePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Role::class => RolePolicy::class
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
// Implicitly grant "Super Admin" role all permissions
// This works in the app by using gate-related functions like auth()->user->can() and #can()
Gate::before(function($user, $ability) {
return $user->hasRole('super-admin') ? true : null;
});
}
}
Here is also my RoleController method for updating the Role model:
/**
* Edit role
*
* #param Edit $request
* #param Role $role
* #return void
*/
public function postEdit(Edit $request, Role $role)
{
# Validation checks happens in the FormRequest
# Session flash also happens in FormRequest
# Update model
$role->update([
'name' => $request->name
]);
# Sync permissions
$permissions = Permission::whereIn('name', $request->input('permissions', []))->get();
$role->syncPermissions($permissions);
return redirect(route('dashboard.roles.edit.get', ['role' => $role->id]))->with('success', 'Changes saved');
}
Does the gate i use to give all permissions have anything to do with the policy not running? Or what am i doing wrong here?
Thanks in advance if anyone can point me in the right direction.
The User model that is included with your Laravel application includes two helpful methods for authorizing actions: can and cant. The can method receives the action you wish to authorize and the relevant model. For example, let's determine if a user is authorized to update a given Role model:
if ($user->can('update', $role)) {
//
}
If a policy is registered for the given model, the can method will automatically call the appropriate policy and return the boolean result. If no policy is registered for the model, the can method will attempt to call the Closure based Gate matching the given action name.
Via Controller Helpers
In addition to helpful methods provided to the User model, Laravel provides a helpful authorize method to any of your controllers which extend the App\Http\Controllers\Controller base class. Like the can method, this method accepts the name of the action you wish to authorize and the relevant model. If the action is not authorized, the authorize method will throw an Illuminate\Auth\Access\AuthorizationException, which the default Laravel exception handler will convert to an HTTP response with a 403 status code:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Role;
use Illuminate\Http\Request;
class RoleController extends Controller
{
/**
* Update the given role.
*
* #param Request $request
* #param role $role
* #return Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Role $role)
{
$this->authorize('update', $role);
// The current user can update the role...
}
}
The Gate::before method in the AuthServiceProvider was the problem. Removed this and rewrote the permissions, policies and some gates to get the error messages from the policies.
Decided to give the role super-admin the permission * and check for this with $user->can() and middleware .....->middlware('can:*') and everything is working now.
I'm trying to use a local scope in one of my models but for this I need to check the user permission, so I try to get the autenticated user by Auth::user().
But it givens me a null because I have an Observer declared for this model, and if I comment the declaration of the Observer the Auth::user() method give me a user authenticated.
There is a correct way or place to declare the Observer and in the model I can get the authenticated user, because I need to use Observers and get in boot method the authenticated user?
Laravel Framework 6.5.2
AppServiceProvider Don't work auth in model
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Conciliador::observe(ConciliadorObserver::class);
Proposta::observe(PropostaObserver::class);
}
AppServiceProvider work auth in model
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//Conciliador::observe(ConciliadorObserver::class);
//Proposta::observe(PropostaObserver::class);
}
Model does not have user logged in when Observer is declared in AppServiceProvider
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
$user = Auth::user();
dd($user); // null if Observer is declared in AppServiceProvider
if($user && $user->tipo == 'admin-gerenciador'){
$conciliadores = $user->conciliadores->pluck('id')->toArray();
static::addGlobalScope('Conciliadores', function (Builder $builder) {
$builder->whereIn('id',$conciliadores);
});
}
}
Don't call Auth::user() anywhere. It may trigger authentication side-effects. It is highly recommended to do that only in controllers and middleware.
But you can safely call Auth::user() if you check by Auth::hasUser() in advance; it checks if the user is ALREADY authenticated.
So your code goes like:
/**
* The "booting" method of the model.
*/
protected static function boot(): void
{
static::addGlobalScope('Conciliadores', function (Builder $query) {
if (Auth::hasUser() && Auth::user()->tipo === 'admin-gerenciador') {
$query->whereKey(Auth::user()->conciliadores->modelKeys());
}
});
}
It's very simple solution. Even middleware is unnecessary.
EDIT
This scope is always available, but actually apply conditions only if the user is already authenticated.
You shouldn't be doing this in your model's boot method like that. The boot method is only called once for the model, not for every model instance. The first time the model is used boot gets called, which would be when you are adding the observer for it in the Service Provider in your case; which would be way before the request is dispatched to a route and through the middleware stack. (There is no session at this point, so no authenticated user.)
You probably want to add your global scope to your model via a middleware.
I just upgraded to Laravel 5.7 and now I am using the built in Email Verification. However there is 2 things I have not been able to figure out and the primary issue is how can I customize the email that is being sent to the user for verifying their email? I also can't figure out how to initiate sending that email if the users changes their email but I can save that for another thread.
When you want to add Email Verification in Laravel 5.7 the suggested method is to implement Illuminate\Contracts\Auth\MustVerifyEmail and use the Illuminate\Auth\MustVerifyEmail trait on the App\User Model.
To make some custom behaviour you can override the method sendEmailVerificationNotification which is the method that notifies the created user by calling the method notify, and passes as a parameter a new instance of the Notifications\MustVerifyEmail class.
You can create a custom Notification which will be passed as a parameter to the $this->notify() within the sendEmailVerificationNotification method in your User Model:
public function sendEmailVerificationNotification()
{
$this->notify(new App\Notifications\CustomVerifyEmail);
}
...then in your CustomVerifyEmail Notification you can define the way the verification will be handled. You can notify created user by sending an email with a custom verification.route which will take any parameters that you want.
Email verification notification process
When a new user signs-up an Illuminate\Auth\Events\Registered Event is emitted in the App\Http\Controllers\Auth\RegisterController and that Registered event has a listener called Illuminate\Auth\Listeners\SendEmailVerificationNotification which is registered in the App\Providers\EventServiceProvider:
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
]
];
The SendEmailVerificationNotification listener checks if the $user – which is passed as a parameter to new Registered($user = $this->create($request->all())) in the Laravel default authentication App\Http\Controllers\Auth\RegisterController – is an instance of Illuminate\Contracts\Auth\MustVerifyEmail which is the name of the trait that Laravel suggests is used in the App\User Model when you want to provide default email verification and also check that $user is not already verified. If all that passes, the sendEmailVerificationNotification method is called on that user:
if ($event->user instanceof MustVerifyEmail && !$event->user->hasVerifiedEmail()) {
$event->user->sendEmailVerificationNotification();
}
I think the simple way to do this is to make a new notification using the docs here: https://laravel.com/docs/5.7/notifications#creating-notifications
Then override the function:
public function sendEmailVerificationNotification()
{
$this->notify(new App\Notifications\CustomEmailNotification);
}
In the users model.
Or you can
php artisan vendor:publish --tag=laravel-notifications
This will copy the templates to the resources/views/vendor/notifications directory and you can modify them there
Unfortunately this email that is sent out is not from a "view", it is a Notification that is built inline actually. This is where it is currently built when needing to be sent off: Illuminate\Auth\Notifications\VerifyEmail#toMail. This particular class has a static callback that can be set to build this email instead of letting it do it.
In a Service Provider in the boot method you will need to assign a callback for this class:
Something "like" this might work:
public function boot()
{
\Illuminate\Auth\Notifications\VerifyEmail::toMailUsing(function ($notifiable) {
// this is what is currently being done
// adjust for your needs
return (new \Illuminate\Notifications\Messages\MailMessage)
->subject(\Lang::getFromJson('Verify Email Address'))
->line(\Lang::getFromJson('Please click the button below to verify your email address.'))
->action(
\Lang::getFromJson('Verify Email Address'),
$this->verificationUrl($notifiable)
)
->line(\Lang::getFromJson('If you did not create an account, no further action is required.'));
});
}
As this is a notification you should have more options on customizing it.
If you want to use your own Notification class you can override the sendEmailVerificationNotification method on the User (Authenticatable) model (this is from the MustVerifyEmail trait).
Second Question:
The VerificationController (App\Http\Controllers\Auth\VerificationController) that you should have has a method named resend (from the trait VerifiesEmails) that looks like a good candidate for this purpose.
You should have routes setup for these verification routes via Auth::routes(['verify' => true]);
Note:
The verification system uses a field on the users table email_verified_at in 5.7 to mark this. You would want to make sure you have this field. When the user changes email address I suppose you could make this null then redirect them to the resend route, to send off the new verification. This will put them into an "unverified" state though until they reverify, if that is what you intend to happen.
Update:
Seems we were going down the right track. I found this SO answer that goes over similar things:
Changing the default “subject” field for the verification email in laravel 5.7
For quick and easy way:
php artisan vendor:publish --tag=laravel-notifications
It's creating a new file in:
\resources\views\vendor\notifications
This is Laravel's email themplate. You can change and customize it.
I'll show you how to customize User verification email using custom view from scratch without using any vendor publish
Step: 1
Create a new notification UserVerifyNotification class. It should extend the VerifyEmail class from the library Illuminate\Auth\Notifications\VerifyEmail;
Code :
use Illuminate\Auth\Notifications\VerifyEmail;
...
class UserVerifyNotification extends VerifyEmail implements ShouldQueue
{
use Queueable;
public $user; //you'll need this to address the user
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($user='')
{
$this->user = $user ?: Auth::user(); //if user is not supplied, get from session
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$actionUrl = $this->verificationUrl($notifiable); //verificationUrl required for the verification link
$actionText = 'Click here to verify your email';
return (new MailMessage)->subject('Verify your account')->view(
'emails.user-verify',
[
'user'=> $this->user,
'actionText' => $actionText,
'actionUrl' => $actionUrl,
]);
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
Step:2
Create the blade view for the email (user-verify.blade.php) inside resources\views\emails
<DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
</head>
<body>
<p>Dear {{$user->name}},</p>
<p>
Please click the button below to verify your email address.
</p>
{{$actionText}}
<p>If you did not create an account, no further action is required.</p>
<p>
Best regards, <br>
{{ config('app.name')}}
</p>
<p>
<hr>
<span class="break-all">
<strong>If you’re having trouble clicking the link, copy and paste the URL below into your web browser:</strong><br/>
<em>{{$actionUrl}}</em>
</p>
</body>
</html>
Step:3
Add the following method inside the User model
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable;
...
...
...
public function sendEmailVerificationNotification()
{
$this->notify(new \App\Notifications\UserVerifyNotification(Auth::user())); //pass the currently logged in user to the notification class
}
}
Explanation
When a new user is registered, the Registered event (Illuminate\Auth\Events) is called.
There is a listener inside EventServiceProvider (App\Providers\EventServiceProvider) that listens to the Registered event. 1. Once registration is completed, it calls SendEmailVerificationNotification method (Illuminate\Auth\Listeners\SendEmailVerificationNotification).
The listener (step-2) calls the sendEmailVerificationNotification() method from the library Illuminate\Auth\Listeners\SendEmailVerificationNotification
We now override the sendEmailVerificationNotification() function in Step: 3 and indicate that we would like to use our own notification class which we made earlier in Step: 1
The notification class gets the 'verification link' and calls the 'user-verify' blade to send email with the necessary link as defined in Step: 2
You'll need to add routes required for verification. Just add the following route in the web.php file
Auth::routes([
'verify' => true,
'register' => true,
]);
Building slightly on the answer by Andrew Earls, you can also publish all the markdown mail components used by the application with this command:
php artisan vendor:publish --tag=laravel-mail
Once that's done you'll have a series of html and markdown files to modify in resources/views/vendor/mail. This will allow you to modify the overall email layout and also 'theme' the CSS. I'd highly recommend having a good read of the Mail docs - Customizing The Components.
CSS theming
As a general email theming quick-start (Laravel 5.7), you can:
Publish the theme with php artisan vendor:publish --tag=laravel-mail .
Copy resources/views/vendor/mail/html/themes/default.css to your own file. e.g resources/views/vendor/mail/html/themes/wayne.css
Edit config/mail.php and where you see 'theme' => 'default' change it to 'theme' => 'wayne'
Edit wayne.css to restyle your emails.
Hope that helps someone.
To send verification email you can just use the next code:
// send new verification email to user
$user->sendEmailVerificationNotification();
In Route File
Auth::routes(['verify' => true]);
In AppServiceProvider.php File
namespace App\Providers;
use App\Mail\EmailVerification;
use Illuminate\Support\ServiceProvider;
use View;
use URL;
use Carbon\Carbon;
use Config;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// Override the email notification for verifying email
VerifyEmail::toMailUsing(function ($notifiable){
$verifyUrl = URL::temporarySignedRoute('verification.verify',
\Illuminate\Support\Carbon::now()->addMinutes(\Illuminate\Support\Facades
\Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
return new EmailVerification($verifyUrl, $notifiable);
});
}
}
Now Create EmailVerification With Markdown
php artisan make:mail EmailVerification --markdown=emails.verify-email
Edit The EmailVerrification as you want and the blade file
class EmailVerification extends Mailable
{
use Queueable, SerializesModels;
public $verifyUrl;
protected $user;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($url,$user)
{
$this->verifyUrl = $url;
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$address = 'mymail#gmail.com';
$name = 'Name';
$subject = 'verify Email';
return $this->to($this->user)->subject($subject)->from($address, $name)->
markdown('emails.verify',['url' => $this->verifyUrl,'user' => $this->user]);
}
}
in the blade file change the design as you want and use verifyUrl to display the verification link and $user to display user information
thanks, happy coding :)
Navigate to these files
vendor/laravel/framework/src/Illuminate/Auth/MustVerifyEmail.php
vendor/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.php
and then customize it.
you can even introduce a constructor in
vendor/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.php
and pass value through vendor/laravel/framework/src/Illuminate/Auth/MustVerifyEmail.php
eg: