Laravel api bypass auth api for specific ip address - laravel

Below is my laravel 8 api route and middleware that I use
Route::group(['middleware'=>['auth:api', 'StripScript'],'prefix' => 'v1'], function(){
Route::get('/list', [ListController::class, 'list']);
});
I this I want to bypass middleware 'auth:api' if I get request from specific ip address so user doesn't require to pass token and it should auto login as one specific user.
Note that I want to bypass auth and not to do 'Ip address Whitelist' as I do have whitelist for this I want to totally by pass for one IP address.

It's not good idea, But any way if you want try this...
Go app/Http/Middleware/Authenticate.php
Add && override handle function like below
public function handle($request, \Closure $next, ...$guards)
{
// check $request is coming from where and set a statement ture/false for a $isComingFromDesiredIp;
if (! $isComingFromDesiredIp) {
$this->authenticate($request, $guards);
}
return $next($request);
}

This should work:
Route::group(['middleware' => in_array(request()->ip(), $arrayOfTrustedIps) ? ['StripScript'] : ['auth:api', 'StripScript'],'prefix' => 'v1'], function(){
Route::get('/list', [ListController::class, 'list']);
});
You should also handle the references to the authed user, if there are any (e.g. auth()->user()->id will throw an error).
IMHO you should authenticate the trusted party via a token sent in header, if you have access to the api call code.
Managing these kind of exceptions in your code can quickly become cumbersome and will be challenging to understand for everyone (including you).

Related

Laravel 8: Using Fortify in APIs

Can Laravel Fortify be used in the context of API? From what I understand, Fortify (although being headless, i.e. doesn't include a UI layer) allows us to customize Login and Register pages, but it automatically redirects to HOME page upon successful authentication. And although the HOME page is customizable, that is not how API logins normally work. It should simply return a success token in JSON format without redirects of any kind.
There is an authenticateUsing function in Fortify, but even that simply allows us to customize authentication logic and not the returned data. Redirection is still performed by Fortify.
How can I use Fortify in the context of REST API?
Note: I'm going to use it from my Vue front-end application. I'm also going to get Sanctum into the game, but before that I just wanted to see if I can do regular token-based authentication using Fortify without having to write my own login, register and logout routes and controller functions.
Authentication can either be Session-based or Token-based.
Laravel Fortify only provides the backend logic nessecery for session-based authentication and therefore is not intended for token-based API authentication.
If you need token-based API authentication, you can use either Sanctum or Passport depending on your needs. But You'll have to write a bit of code, in either case.
If you decide to go with Laravel Passport, I have a boilerplate project that might be of use: https://github.com/pktharindu/laravel-api-boilerplate-passport
Just set 'Accept' header with 'application/json' or 'application/javascript' then fortify will response json formatted body not redirection.
by the way, use Sanctum instead of Passport for SPA is easier to keep token securely. google about where to store API token for SPA then you will find out why.
The redirects reason in my case was the default laravel RedirectIfAuthenticated provider.
By default in laravel 8 that provider looks like
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string[]|null ...$guards
* #return mixed
*/
public function handle($request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
just replace default foreach by the following code with that:
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
if ($request->expectsJson()) {
return response()->json([
'error' => 'Already authenticated.'
], 406);
}
return redirect(RouteServiceProvider::HOME);
}
}
Don't forget that for $request->expectsJson() to work fine you should include Accept: application/json in the request headers.

Auth::user() is null in new route

i'm using laravel 6 and have 2 route in my app; index and dashboard.
My routes/web is:
Auth::routes();
Route::middleware(['auth'])->group(function () {
Route::get('/index', 'todoApp\TodoController#index')->name('index');
Route::get('/dashboard', 'todoApp\Dashboard#dashboard')->name('dashboard');
});
i added dashboard route recently.
Auth::user() is null when i dump it in dashboard route but doesn't in index. What's the
Your Controller is instantiated before the middleware stack has ran; this is how Laravel can know what middleware you have set via the constructor. Because of this you will not have access to the authenticated user or sessions at this point. Ex:
public function __construct()
{
$this->user = Auth::user(); // will always be null
}
If you need to assign such a variable or access this type of information you would need to use a controller middleware which will run in the stack after the StartSession middleware:
public function __construct()
{
$this->middleware(function ($request, $next) {
// this is getting executed later after the other middleware has ran
$this->user = Auth::user();
return $next($request);
});
}
When the dashboard method is called, the middleware stack has already passed the Request all the way through to the end of the stack so all the middleware needed for Auth to be functioning and available has already ran at that point which is why you have access to Auth::user() there.
I think that this has something to do with the 'web' middleware. If you take a look into the Kernel.php (In app\Http) you will find the web middleware group.
This will show you that it actually calls a middleware called StartSession. Based on your route file (where web is not included as a middleware) I would think that you don't have a session in your Controller and there for no access to it.
I don't quite understand why this only happens in your /dashboard route, because the issue should also be in your /index route (unless you added the web middleware somewhere in your TodoController).
I think that this should do the trick:
Route::middleware(['web', 'auth'])->group(function () {
Route::get('/index', 'todoApp\TodoController#index')->name('index');
Route::get('/dashboard', 'todoApp\Dashboard#dashboard')->name('dashboard');
});
If you fire php artisan make:auth command.
It's doesn't matter where you define because of it's only define auth route
Route::middleware(['auth'])->group(function () {
Route::get('/index', 'todoApp\TodoController#index')->name('index');
Route::get('/dashboard', 'todoApp\Dashboard#dashboard')->name('dashboard');
});
Auth::routes();

Laravel Routing Order Multiple "Homepages" Based On Auth

I have a Laravel routing file (web.php) like so:
Route::group(['middleware' => ['auth']], function () {
Route::get('/', function () {
return 'Hello World';
});
});
Route::get('/', 'Auth\LoginController#showLoginForm')->name('login');
In my application the unauthenticated homepage must go to the login screen. When authenticated, the homepage changes. However, if I run this code and login - I get an infinite redirect because the LoginController has this line:
protected $redirectTo = '/';
So basically, even thought Laravel should read the routes file top to bottom, it seems this isn't applying in my case (maybe because of the ::group()) or I am doing something wrong.
How can I accomplish a homepage that goes to the login form while also redirecting back to the same url (/) for authenticated users for a different authenticated view (and no redirect loop).
Thanks!
I would handle this inside a guest middleware that checks for an authenticated user. For this case you'll want an authenticated guard check first:
use Illuminate\Contracts\Auth\Guard;
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
return redirect('/home');
}
return $next($request);
}
Apply the guest middleware to the login endpoint:
Route::get('/', 'Auth\LoginController#showLoginForm')
->middleware('guest')
->name('login');
Note: Only apply the guest middleware to guest routes, otherwise you run the risk of redirect loops.
Why not handle this in your view?
You can simply remove one of the route declaration from your web.php.
Then go ahead with conditional rendering.
#auth
Show Home page
#endauth
#guest
Show Login
#endguest
I guess middleware does not prevent request from hitting the routes,
It rather prevents request from going through.
You can't use middleenter code hereware to decide with route to hit, thereby the loop.
When you're thinking in terms of bottom down processing, laravel has already registered every declared routes.

Problem getting authenticated user with Laravel Lumen + Passport

I am using LumenPassport (https://github.com/dusterio/lumen-passport) and I followed a few tutorials listed here.
I used a combination of these tutorials as well as a heck of google and stackoverflow searches to achieve what I have thus far:
http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/
http://esbenp.github.io/2015/05/26/lumen-web-api-oauth-2-authentication/
https://blog.pusher.com/make-an-oauth2-server-using-laravel-passport/
What I achieved so far
1. Using password grant to get an access & refresh token
2. Storing these tokens in a secure http only cookie
3. Retrieving these tokens in Lumen's AuthServiceProvider
What I am unable to do
1. Getting the authenticated user with the AccessToken
I am trying to access either of these endpoints:
$router->group(['middleware' => 'auth:api'], function () use ($router) {
$router->get('/', function () use ($router) {return $router->app->version();});
$router->post('/logout', '\App\Auth\LoginController#logout');
});
I will immediately get an unauthorized error.. After some deep diving, the error comes from Authenticate.php which I know is called after AuthServiceProvider. I took a look at AuthServiceProvider and according to Lumen's documentation, this is how the boot method should looks like. Of course it is using the "api" driver and I had to switch it to "passport" for it to work.
AuthServiceProvider.php
public function boot()
{
$this->app['auth']->viaRequest('passport', function ($request) {
// dd("test") // this works
// dd(Auth::user());
// dd($request->user());
// dd(Auth::guard('api')->user());
});
}
Authenticate.php
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
$status = Response::HTTP_UNAUTHORIZED;
return response()->json(['success' => false, 'status' => $status, 'message' => 'HTTP_UNAUTHORIZED'], $status);
}
return $next($request);
}
From here, I am still unable to get any of the authenticated user's information. I have made sure to access these endpoints with Postman with the appropriate Authorization headers.
The reason why I need to retrieve the user is because I hope that in my logout method, I will be able to then retrieve the accessToken of that authenticated user and revoke the token and clear the cookies.
LoginController.php
public function logout()
{
// Get the accessToken from Auth
// Need to fix AuthServiceProvider first
$accessToken = $this->auth->user()->token();
$refreshToken = $this->db
->table('oauth_refresh_tokens')
->where('access_token_id', $accessToken->id)
->update([
'revoked' => true,
]);
$accessToken->revoke();
$this->cookie->queue($this->cookie->forget(self::REFRESH_TOKEN));
}
At that point you cannot use Auth::user() since that function is the functionality for resolving that. So what you need to do is extract the bearer token with $request->bearerToken() and use that to retrieve your user.
Update
I took a look at your code and I would recommend the following:
An API is recommended to be 'stateless' meaning that it should not persist any state (i.e. cookies). It is far better to pass the access token with each request and let the application that accesses your API handle the tokens. Therefore I would recommend to remove the log-out functionality. Then you can do the following in your AuthServiceProvider:
if ($token_exists) {
$user = User::find($token->user_id);
return $user;
}

How to have a route for both authenticated users and non-authenticated users

I have an issue with auth:api middleware!
We have a request that is accessible for both authenticated users and non-authenticated users when I define a route like this for non-authenticated users:
Route::post('{user}/leads', 'UsersController#getContact');
It's ok everything work fine when a guest user requesting this route.
is and I can access user with $request->user();
but if pass token with bearer header and get the user with $request->user() of course it doesn't work! because we didn't use auth:api on this route, and if we do we can't access this route with guest users!
So I can't find a way that we define one route for both authenticated users that if user is authenticated we get $request->user() and none authenticated users can access that route too!
Thanks in advance.
I found a way to do that I just wrote this:
$middleware = ['api'];
if (\Request::header('Authorization'))
$middleware = array_merge(['auth:api']);
Route::group(['prefix' => 'v1', 'namespace' => 'Api', 'middleware' => $middleware], function () {
//routes here
});
In api.php route file and it works fine.
Thanks
This is because Auth uses the default web guard. You have to check the api guard manually:
$user = Auth::user() ?? Auth::guard("api")->user();
Then you don't use any auth middleware. $user will be null if the user is a guest, otherwise it should be set.
The solution I used was to create a new middleware for auth:
public function handle($request, Closure $next, ...$guards)
{
try
{
$this->authenticate($request, $guards);
}
catch(AuthenticationException $ex)
{
}
return $next($request);
}
and in at the BOTTOM of my route I did:
Route::middleware('auth_optional:api')->group(function () {
Route::get('services', [ServiceController::class,'index']);
});
This way if Auth was needed ,it would assign the correct user to request, otherwise it would proceed as guest. I did need to do a $request->user() === null to make sure the user is guest
If you want the routes are visible to only Authenticate users you can put all routes in auth middleware that is default provided by laravel you can put like this:-
enter code here
Route::group(['middleware' => ['auth']], function () {
Route::post('{user}/leads', 'UsersController#getContact');
});
And if you want to show the route both authenticate and non-authenticate user
You can simply put outside the middleware
Lik that:-
Route::match(['get', 'post'], '/cms-page','CmsController#cms');
Hope you understand
I would like to use additional routes both authenticated and non-authenticated users,
But regarding the topic I add one simple way :
On the __constructor function of the Controller add those lines :
$authorizationHeader = \request()->header('Authorization');
if(isset($authorizationHeader)) {
$this->middleware('auth:api');
}
But I don't concentrate this way as best practice, this brokes Single Responsibility Principle.
If you are using Laravel Passport then this way can be more cleaner.
In controller you can directly get user by
$user = $request->user('api');
This will get you the authenticated user and if bearer token is invalid then it will not throw 'unauthenticated' error but result in null user.
Reference: How to authenticate user without auth:api middleware in laravel 5.3?

Resources