I am writing an API using Laravel 8 .. I have followed the documentation for implementing email verification, but I want to modify the link that is sent with the email.
My API and Frontend are completely seperate, so the link that Laravel creates needs to be changed to point to my frontend.
I have implemented as per in my AuthServiceProvider;
public function boot(): void
{
$this->registerPolicies();
VerifyEmail::toMailUsing(function ($notifiable, $url) {
return (new MailMessage)
->subject('Verify Email Address')
->line('Click the button below to verify your email address.')
->action('Verify Email Address', $url);
});
}
I am working on localhost so please excuse the URL's .. But the URL that laravel outputs is as follows;
http://localhost/api/email/verify/217gd8b5b-1e23-4450-8b3b-a9c7610b16ed?expires=1625027126&hash=24499f2ba77786684dab8f4d71832d71d86be69e0&signature=e5ac2a5aa6c941ce36ef70d842d8efcea7ed79fc72597a1e44cd36c566fd71b34
I need to change that to something like;
http://localhost:3000/verifyemail/217gd8b5b-1e23-4450-8b3b-a9c7610b16ed?expires=1625027126&hash=24499f2ba77786684dab8f4d71832d71d86be69e0&signature=e5ac2a5aa6c941ce36ef70d842d8efcea7ed79fc72597a1e44cd36c566fd71b34
Is that possible at all?
Cheers,
As per the documentation, you need to set up a route with a specific naming convention; verification.verify.
You can add this route to your web routes like so:
Route::get('/verifyemail/{id}/{hash}', function () {
return;
})->name('verification.verify');
Laravel doesn't care that the actual frontend is detached for parsing the link.
Second, you need to verify the request via your API from your detached frontend. You need to forward the request to the API and use the Illuminate\Foundation\Auth\EmailVerificationRequest as the Request instance to fulfill the verification.
The following example is just a pseudo example, as this might require some tinkering depending on your frontend implementation, but you would hopefully get the idea. This route of course belongs to your api routes.
use Illuminate\Foundation\Auth\EmailVerificationRequest;
Route::get('/verifyemail/{id}/{hash}', function (EmailVerificationRequest $request) {
$request->fulfill();
// Just return a 201 (or 200 if it pleases you better)
return response()->json(null, 201);
// Don't use the verification.verify route name here
})->middleware(['auth', 'signed']);
That should do it.
Related
After following the installation for enabling the new built-in email verification, all is working good (sending email after registration and clicking the activation enable the account).
But, I'm faced with the case where the user must be logged-in in order for the verification process to engage. Meaning, if the user is not logged in prior to use the verification link, he will be redirected to the login page and then be presented the /resources/view/auth/verify.blade.php page.
I am reaching out to the community to see if this it intentional, a bug or something I'm doing wrong? Did someone run in the same situation?
The site I'm setting up have public access to most pages, but restricted to some (for now the user portal). I set up the routes/web.php as follow:
// Authentication
Auth::routes(['verify' => true]);
Route::group(['middleware' => ['auth', 'verified'], 'as' => 'portal.', 'prefix' => '/portal'], function () {
Route::get('/', 'PortalController#index');
Route::get('/profile', 'PortalController#index')->name('profile');
Route::get('/orders', 'PortalController#index')->name('orders');
});
By tracing the verification process, I was able to find out the process is forcing a log-in in the VerificationController constructor via the middleware shown below.
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
By commenting the first line or adding ->except('verify'), the log-in page is not shown but an error is thrown at the Traits VerifiesEmails method Verify like below, since the user is obviously not logged it (the $request->user() is null).
public function verify(Request $request)
{
if ($request->route('id') == $request->user()->getKey() &&
$request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('verified', true);
}
My question, is there a way to get it to work without being loged-in beforehand, or is this the way the Verification process is implemented in 5.7? ... or what am I doing wrong?
is there a way to get it to work without being loged-in beforehand, or
is this the way the Verification process is implemented in 5.7? ... or
what am I doing wrong?
This is the way the Verification process is implemented in Laravel 5.7. Laravel uses signed URLs for verification. The URL is generated with an id parameter (id as user ID) and when the user clicks on the verification link, 3 checks are done:
Is the signature valid? (signed middleware)
What's the user ID in the signature? That's the ID that would ultimately be validated
Does the currently logged in user have the same ID?
You can always remove the third check by overriding the verify method in your VerificationController like so:
public function verify(Request $request)
{
$userId = $request->route('id');
$user = App\User::findOrFail($userId);
if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
return redirect($this->redirectPath())->with('verified', true);
}
Brief:
Actually, I'm little bit confused while using Laravel api route file.
Question:
If I need to access the data of my web site in other platform (like android app) that is made using laravel, then should I create a different route in api.php file?
If yes then I will be declaring two routes and controllers for each request, first in web.php and second in api.php. Is it correct?
Basically, I want to ask that how I can make an api, so that I can access the data in website as well as in other platforms?
I was searching for a good tutorial for this, but I didn't got a good one.
Ideally the API routes and Web routes should be completely different but if you want it to be same then instead of defining routes in different file you can add routes only in web.php and add a special parameter from your client and in controller if you are getting the parameter then return the JSON Object or else return the view.
Eg.
web.php
Route::get('getUsers','UserController#getUsers');
UserController.php
...
public function getUsers(Request $request)
{
...
if ($request->has('api')) {
return $users; //API Route (Laravel will by Default return the JSON Response no need to do json_encode)
}
return view('pages.user_list'); //Normal Routes hence returning View
}
...
Requests
Normal Request
<Yourdomain>/getUsers
API Request
<Yourdomain>/getUsers?api=true
I hope that helped...
Write your api routes in api.php and web routes in web.php.
The Api routes always have the name api in the routes thus you can differentiate the routes., I mentioned here because as #Akshay Khale mentioned an example with query parameter.
if you want to use the same controller for both API and Web, Api Requests always have the Header Content-Type : Json and "Accept":"application/json" so in your controller you can do it as below.
public function getUsers(Request $request)
{
...
if ($request->wantsJson()) {
return response()->json($users, 200); //here why we are extending response object because using json() method you can send the status code with the response.
}
return view('pages.user_list'); //Normal Routes hence returning View
}
for the laravel 5.6 and above the above answers won't work for me , so here is my 2 cents.
I have put the routes in web.php and api.php and normal no any magic tricks .
public function getUsers(Request $request)
{
....
if( $request->is('api/*')){
...
return response()->json($user_data, 200);
}
...
return view('users', ['users_data'=>$user_data]);
}
It will return json output for
127.0.0.1:8000/api/users
and normal view in html for
127.0.0.1:8000/users
Im building an API in Laravel 5.3. In my routes/api.php file I have 3 endpoints:
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
Several services will call these routes directly, however, when a request comes in from some services, the input data needs to be processed before it can hit these endpoints.
For example, if a request comes in from JIRA, I need to process the input before it hits these endpoints.
Im thinking that the simplest way to do this would be to have a 4th endpoint like the below:
Route::post('/notify/v1/jira', 'Api\Notify\v1\JiraFormatter#formatJiraPost');
The idea being to hit the /notify/v1/jira endpoint, have the formatJiraPost method process the input and then forward the request to /notify/v1/notifications (/alerts, /incidents) as required.
How can I have the /notify/v1/jira endpoint forward the request to the /notify/v1/notifications endpoint?
Do you see a better way to do this?
Depending how your app will work, you could always have your services pointing to /notify/v1/jira and then do the processing like you suggested.
Another alternative is have the JIRA service pointing at the same routes as all the other services but to use a Before middleware group to pre-process your data. Something like
Route::group(['middleware' => ['verifyService']], function () {
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
});
You could check in your middleware your service.
<?php
namespace App\Http\Middleware;
use Closure;
class verifyService
{
public function handle($request, Closure $next)
{
//You could use switch/break case structure
if (isJIRA($request)) {
//Do some processing, it could be outsourced to another class
//$JiraPost = new formatJiraPost($request);
//Keep the requesting going to the routes with processed data
return $next($request);
}
//You could add extra logic to check for more services.
return $next($request);
}
protected function isJIRA(Request $request){
//Logic to check if it is JIRA.
}
}
I've tried to exclude requests from another localhost server (http://localhost:8080/order/placeorder) to another one localhost server (http://localhost:8000)
I don't want to disable all csrf protection by removing
\App\Http\Middleware\VerifyCsrfToken::class in Illuminate\Foundation\Http\Kernel.php
I've tried to modify app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'http://localhost:8080/*',
'http://localhost:8080',
'/order/placeorder/*',
'http://localhost:8080/order/placeorder'
];
and I also tried this way
private $openRoutes = [
'http://localhost:8080/*',
'http://localhost:8080',
'/order/placeorder/*',
'http://localhost:8080/order/placeorder'
];
public function handle($request, Closure $next)
{
//add this condition
foreach($this->openRoutes as $route) {
if ($request->is($route)) {
return $next($request);
}
}
return parent::handle($request, $next);
}
But I still got this error
TokenMismatchException in VerifyCsrfToken.php
Can anyone suggest me what should I do and what I've done wrong?
The exceptions are routes within your own application that are excluded, not the URLs of servers that are requesting it. You will never put localhost, http, or any domain in these exceptions in normal circumstances. If you wish for a request by an external server to be accepted, I would disable CSRF protection for the routes it is accessing (because you want a cross-site request, that's what CSRF prevents).
For example, if you want any external server to be able to send a POST request to /order/placeorder, you would simply add that route to the exclusion. You also need to add any other route you want it to be able to access. If there are a lot, there are other more manageable ways to do this with middleware as well.
To authenticate the server making the request, it should send a token to verify itself. You can create a static token for this purpose (like an API key), or possibly use an OAuth implementation of some sort with access/refresh tokens - there is a package for Laravel for this that makes it easy.
I'm having some trouble making middleware that checks if the user owns the resource being requested.
For example, if the user goes to /playlists/1/edit, and they do not own playlist 1, it should display a 401 error.
Here's what I have so far:
class CheckOwnership {
public function handle(Request $request, Closure $next)
{
if (Playlist::find($request->route()->parameters()['playlists'])->user_id !== $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
}
This is terrible and only works for the Playlist resource, but I can't find any better way of doing this.
Thanks
This can easily be achieved with the newly added Form Request Validation.
You can see it in detail here (Authorizing Form Requests):
http://laravel.com/docs/5.0/validation#form-request-validation
The example given is actually about a user attempting to edit a comment they own.
Extract:
The form request class also contains an authorize method. Within this
method, you may check if the authenticated user actually has the
authority to update a given resource. For example, if a user is
attempting to update a blog post comment, do they actually own that
comment?
In your case, simply return false from the authorize method if they do no own the Playlist.
Currently, Laravel 5 does not support passing parameters to middlewares. I use sessions instead.
On your playlist controller, fetch the owner of the playlist and store it on a session. Let's say you have a playlists table with columns userID and playlistID.
public function __construct($playlistID){
$owner = Playlist::where('playlistID',$playlistID)->pluck('userID');
Session::put('OWNER',$owner);
$this->middleware('CheckOwnership',['only'=>'edit']); // apply it to edit function only, assuming you are using a route resource
}
Then, simply retrieve it on your middleware handle function.
public function handle(Request $request, Closure $next)
{
if (Session::get('OWNER') != $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
So far, this is a simple workaround. We have to wait till Otwell considers filter-like middlewares. Hope this helps!
For those using Laravel 8, I recommend using using Policies. This would let you organize authorization logic for specific models (e.x. the Playlist model for #ntzm).
So for example, a PlaylistPolicy class can be generated,
php artisan make:policy PlaylistPolicy --model=Playlist
and then the update function could look like this.
public function update(User $user, Playlist $playlist)
{
return $user->id === $playlist->user_id;
}
There are multiple way of enforcing this policy. If you would like to use middleware, Laravel has the can middleware that can enforce policies, so new middleware won't need to be written. In your route file this would look something like this,
Route::put('playlists/{playlist}/edit', ...)
->middleware(['can:update,playlist']);
Note: If the --model option isn't used, the policy will have to be registered manually, and example policy methods won't be automatically generated.