I need to implement subdomain for api routes, but I'm getting 404 error
I have APP_URL set to http://example.com
I've configured subdomain in RouteServiceProvider
protected function mapApiRoutes()
{
Route::domain('api.example.com')
->prefix('/api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
I see that problem is in Illuminate\Routing\Matching\HostValidator when it calls $request->getHost() it returns 'example.com', not 'api.example.com'. If i change APP_URL to http://api.example.com it works well.
class HostValidator implements ValidatorInterface
{
/**
* Validate a given rule against a route and request.
*
* #param \Illuminate\Routing\Route $route
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function matches(Route $route, Request $request)
{
$hostRegex = $route->getCompiled()->getHostRegex();
$host = $request->getHost();
if (is_null($hostRegex)) {
return true;
}
return preg_match($hostRegex, $request->getHost());
}
}
Looks like i missed some configuration, but I haven't found any additional configuration requirements in laravel docs.
"laravel/framework": "^7.12"
ADDITIONAL: so currently i can see that laravel redirects api.example.com to example.com, so that's why I'm getting host validation error.
The next question is - Why it does redirect? =)
I don't think it will help somebody, but the answer was in one middleware which I've found in project. It was redirecting all invalid hosts (non APP_URL) to APP_URL:
if (auth()->user() == null && $request->getSchemeAndHttpHost() != $this->config->get('app.url')) {
$additional = '';
if ($request->input()) {
$additional .= '?' . http_build_query($request->input());
}
return redirect()->secure($this->config->get('app.url') . $request->getPathInfo() . $additional);
}
return $next($request);
Related
there are 2 sumbdomains in my laravel app. One api another storage. I login by api.exm.com/login but when I want to sotre a file by storage.exm.com/image I get 403. How can I solve it?
Here is a section of my RouteServiceProvider:
protected function mapApiRoutes()
{
Route::middleware(['api', 'return.json'])
->domain(subdomain(env('API_SUBDOMAIN', 'api')))
->namespace($this->namespace . '\Api')
->group(base_path('routes/api.php'));
}
protected function mapStorageRoutes()
{
Route::middleware(['api', 'return.json'])
->domain(subdomain(env('STORAGE_SUBDOMAIN', 'storage')))
->namespace($this->namespace . '\Storage')
->group(base_path('routes/storage.php'));
}
I found my problem. My authorize function in StoreImageRequest always returned false. :))
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
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');
}
I wrote a very simple middleware, like this:
class CheckToken
{
private $token='xxx';
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->tokenz == $this->token) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
Then I register it trough kernel.php, like this:
protected $routeMiddleware = [
.....
'CheckToken' => \App\Http\Middleware\CheckToken::class,
];
then Ive a very simple function in a controller guarded by this controller:
public function __construct()
{
$this->middleware('CheckToken');
}
public function push()
{
return view('home');
}
Now starts what is not clear to me:
how can i "protect" my page using this simple method?
I've tried to put this tag on the header of page but it seems to not works, maybe im in the wrong path:
<meta name="tokenz" content="xxx">
I put it even in the body but no results.
what ive misunderstood?
I believe you need to add the middleware call to the actual route:
use App\Http\Middleware\CheckAge;
Route::get('admin/profile', function () {
//
})->middleware(CheckAge::class);
This was extracted from the Laravel 5.7 documentation: Middleware - Assigning Middleware to Routes
sorry i can't create a comment. but just want to help.
does $request passed a tokenz?
you can use ?tokenz=blablabla
or you can change your method to get the tokenz
I have a controller with the following in the constructor:
$this->middleware('guest', ['except' =>
[
'logout',
'auth/facebook',
'auth/facebook/callback',
'auth/facebook/unlink'
]
]);
The 'logout' rule (which is there by default) works perfectly but the other 3 rules I have added are ignored. The routes in routes.php look like this:
Route::group(['middleware' => ['web']],function(){
Route::auth();
// Facebook auth
Route::get('/auth/facebook', 'Auth\AuthController#redirectToFacebook')->name('facebook_auth');
Route::get('/auth/facebook/callback', 'Auth\AuthController#handleFacebookCallback')->name('facebook_callback');
Route::get('/auth/facebook/unlink', 'Auth\AuthController#handleFacebookUnlink')->name('facebook_unlink');
}
If I visit auth/facebook, auth/facebook/callback or auth/facebook/unlink whilst logged in I get denied by the middleware and thrown back to the homepage.
I've tried specifying the 'except' rules with proceeding /'s so they match the routes in routes.php exactly but it makes no difference. Any ideas why these rules are being ignored, whilst the default 'logout' rule is respected?
Cheers!
You need to pass the method's name instead of the URI.
<?php
namespace App\Http\Controllers;
class MyController extends Controller {
public function __construct() {
$this->middleware('guest', ['except' => [
'redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink'
]]);
}
}
Since Laravel 5.3, you can use fluent interface to define middlewares on controllers, which seems cleaner than using multidimensional arrays.
<?php
$this->middleware('guest')->except('redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink');
I solved this issue in my Middleware by adding this inExceptArray function. It's the same way VerifyCsrfToken handles the except array.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class MyMiddleware
{
/**
* Routes that should skip handle.
*
* #var array
*/
protected $except = [
'/some/route',
];
/**
* Determine if the request has a URI that should pass through.
*
* #param Request $request
* #return bool
*/
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->is($except)) {
return true;
}
}
return false;
}
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// check user authed or API Key
if (!$this->inExceptArray($request)) {
// Process middleware checks and return if failed...
if (true) {
// Middleware failed, send back response
return response()->json([
'error' => true,
'Message' => 'Failed Middleware check'
]);
}
}
// Middleware passed or in Except array
return $next($request);
}
}
If you are trying to follow the Laravel Documentation, an alternative solution to this is suggested by adding routes to the $except variable in the /Http/Middleware/VerifyCsrfToken.php file. The documentation says to add them like this:
'route/*'
But I found the only way to get it to work is by putting the routes to ignore like this:
'/route'
When assigning middleware to a group of routes, you may occasionally need to prevent the middleware from being applied to an individual route within the group. You may accomplish this using the withoutMiddleware method:
use App\Http\Middleware\CheckAge;
Route::middleware([CheckAge::class])->group(function () {
Route::get('/', function () {
//
});
Route::get('admin/profile', function () {
//
})->withoutMiddleware([CheckAge::class]);
});
for more information read documentation laravel middleware
Use this function in your Controller:
public function __construct()
{
$this->middleware(['auth' => 'verified'])->except("page_name_1", "page_name_2", "page_name_3");
}
*replace page_name_1/2/3 with yours.
For me it's working fine.
I have this solved, and here's what I am doing. Aso, I just realized this is very similar to what cmac did in his answer.
api.php
Route::group(['middleware' => 'auth'], function () {
Route::get('/user', 'Auth\UserController#me')->name('me');
Route::post('logout', 'Auth\LoginController#logout')->name('logout');
});
LoginController.php
class LoginController extends Controller
{
use AuthenticatesUsers, ThrottlesLogins;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
// ...
/**
* If the user's session is expired, the auth token is already invalidated,
* so we just return success to the client.
*
* This solves the edge case where the user clicks the Logout button as their first
* interaction in a stale session, and allows a clean redirect to the login page.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$user = $this->guard()->user();
if ($user) {
$this->guard()->logout();
JWTAuth::invalidate();
}
return response()->json(['success' => 'Logged out.'], 200);
}
}
Authenticate.php
class Authenticate extends Middleware
{
/**
* Exclude these routes from authentication check.
*
* Note: `$request->is('api/fragment*')` https://laravel.com/docs/7.x/requests
*
* #var array
*/
protected $except = [
'api/logout',
];
/**
* Ensure the user is authenticated.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
foreach ($this->except as $excluded_route) {
if ($request->path() === $excluded_route) {
\Log::debug("Skipping $excluded_route from auth check...");
return $next($request);
}
}
// code below here requires 'auth'
{ catch ($e) {
// ...
}
}
I over-engineered it slightly. Today I only need an exemption on /api/logout, but I set the logic up to quickly add more routes. If you research the VerifyCsrfToken middleware, you'll see it takes a form like this:
protected $except = [
'api/logout',
'api/foobars*',
'stripe/poop',
'https://www.external.com/yolo',
];
That's why I put that "note" in my doc above there. $request->path() === $excluded_route will probably not match api/foobars*, but $request->is('api/foobars*') should. Additionally, a person might be able to use something like $request->url() === $excluded_route to match http://www.external.com/yolo.
You should pass the function name to 'except'.
Here's an example from one of my projects:
$this->middleware('IsAdminOrSupport', ['except' => [
'ProductsByShopPage'
]
]);
This means the middleware 'IsAdminOrSupport' is applied to all methods of this controller except for the method 'ProductByShopPage'.
I have a middleware like this:
<?php
namespace App\Http\Middleware;
use App\Contracts\PermissionsHandlerInterface;
use Closure;
class PermissionsHanlderMiddleware {
public $permissionsHandler;
function __construct(PermissionsHandlerInterface $permissionsHandler) {
$this -> permissionsHandler = $permissionsHandler;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
$routeAction = $request->route()->getActionName();
/*
do some operations
*/
return $next($request);
}
}
but $request->route() always returns null, I think its because the router hasn't been dispatched with the request.
Note: I added my middleware to Kernal.php global middlewares to run before each request as the following
protected $middleware = [
.
.
.
'App\Http\Middleware\PermissionsHanlderMiddleware',
];
I want to get route action name before the execution of $next($request) to do some permission operations. How can i do this ?
You cannot get the route action name if the router has not yet been dispatched. The router class has not yet done its stuff - so you cannot do $router->request() - it will just be null.
If it runs as routeMiddleware as $routeMiddleware - then you can just do $router->request()
You can get the URI string in the middleware before the router has run - and do some logic there if you like: $request->segments(). i.e. that way you can see if the URI segment matches a specific route and run some code.
Edit:
One way I can quickly think of is just wrap all your routes in a group like this:
$router->group(['middleware' => 'permissionsHandler'], function() use ($router) {
// Have every single route here
});
This is the solution I did in my project:
...
public function handle($request, Closure $next) {
DB::beginTransaction();
$nextRequest = $next($request); //The router will be dispatched here, but it will reach to controller's method sometimes, so that we have to use DB transaction.
$routeName = $request->route()->getRouteName();
if ($checkPassed) {
DB::commit();
return $nextRequest;
} else {
DB::rollback();
}
}
This is also fine.
$request->path(); // path
$request->route()->getName()//name of the route