disable access to Laravel API from browser - laravel

How can I restrict direct access on GET route?
example localhost/api/helloworld
if I send a request with postman I want to get a response, but if I try open URL in a browser I need redirect to the homepage or give some 403 error?
should I use some header to restrict it or there is another way to do it in Laravel?

The standard way to do this is use token. Laravel provides you API authentication, follow this link API Authentication (Passport)

use the following code to detect the requesting client from headers.
public function isBrowserRequest()
{
$browsers = ['Opera', 'Mozilla', 'Firefox', 'Chrome', 'Edge'];
$userAgent = request()->header('User-Agent');
$isBrowser = false;
foreach($browsers as $browser){
if(strpos($userAgent, $browser) !== false){
$isBrowser = true;
break;
}
}
return ['result' => $isBrowser];
}

Related

how to check if user is authenticated with passport (get user from token using laravel-passport)

I am using Passport to log in users to a Laravel API endpoint, users get authenticated using their social accounts (google, facebook) using laravel-socialite package.
the workflow of logging users in and out works perfectly (generating tokens...Etc). The problem is I have a controller that should return data based on whether there is a user logged in or not.
I do intercept the Bearer token from the HTTP request but I couldn't get the user using the token (I would use DB facade to select the user based on the token but I am actually looking whether there is a more clean way already implemented in Passport)
I also don't want to use auth:api middleware as the controller should work and return data even if no user is logged in.
this is the api route:
Route::get("/articles/{tag?}", "ArticleController#get_tagged");
this is the logic I want the controller to have
public function get_tagged($tag = "", Request $request)
{
if ($request->header("Authorization"))
// return data related to the user
else
// return general data
}
Assuming that you set your api guard to passport, you can simply call if (Auth::guard('api')->check()) to check for an authenticated user:
public function get_tagged($tag = "", Request $request)
{
if (Auth::guard('api')->check()) {
// Here you have access to $request->user() method that
// contains the model of the currently authenticated user.
//
// Note that this method should only work if you call it
// after an Auth::check(), because the user is set in the
// request object by the auth component after a successful
// authentication check/retrival
return response()->json($request->user());
}
// alternative method
if (($user = Auth::user()) !== null) {
// Here you have your authenticated user model
return response()->json($user);
}
// return general data
return response('Unauthenticated user');
}
This would trigger the Laravel authentication checks in the same way as auth:api guard, but won't redirect the user away. In fact, the redirection is done by the Authenticate middleware (stored in vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php) upon the failure of the authentication checking.
Beware that if you don't specify the guard to use, Laravel will use the default guard setting in the config/auth.php file (usually set to web on a fresh Laravel installation).
If you prefer to stick with the Auth facade/class you can as well use Auth::guard('api')->user() instead or the request object.
thanks to #mdexp answer
In my case I can resolve my problem with using
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
}
In my controller.

Vue API Calls and Laravel Middleware

Is it possible to globally set a listener on API calls made with Axios in Vue? The Laravel back-end has middleware set up on each endpoint that will either give the requested data or return a message saying that they need to check their messages. My goal is to capture that message and redirect the user to the page to view their message. I can't think of a way to do this other than setting something on each function that checks for the message and responds accordingly. There are hundreds of functions and that it wouldn't be a clean solution.
Any and all recommendations are welcome!
Using Axios Interceptors you can do something along these lines:
this.$http.interceptors.response.use(response => () {
// Redirect to a new page when you send
// custom header from the server
if (response.headers.hasOwnProperty('my-custom-header')) {
window.location.href = '/another-page';
}
// Or when you get a specific response status code
if (response.status === 402) {
window.location.href = '/another-page';
}
// Or when the response contains some specific data
if (response.data.someKey === 'redirect') {
window.location.href = '/another-page';
}
// ...or whatever you want
});

How to protect against CSRF on a static site?

I have a static website, being served from a CDN, that communicates with an API via AJAX. How do I protect against CSRF?
Since I do not have control over how the static website is served, I cannot generate a CSRF token when someone loads my static website (and insert the token into forms or send it with my AJAX requests). I could create a GET endpoint to retrieve the token, but it seems like an attacker could simply access that endpoint and use the token it provides?
Is there an effective way to prevent against CSRF with this stack?
Additional details: authentication is completely separate here. Some of the API requests for which I want CSRF protection are authenticated endpoints, and some are public POST requests (but I want to confirm that they are coming from my site, not someone else's)
I could create a GET endpoint to retrieve the token, but it seems like an attacker could simply access that endpoint and use the token it provides?
Correct. But CSRF tokens are not meant to be secret. They only exist to confirm an action is performed in the order expected by one user (e.g. a form POST only follows a GET request for the form). Even on a dynamic website an attacker could submit their own GET request to a page and parse out the CSRF token embedded in a form.
From OWASP:
CSRF is an attack that tricks the victim into submitting a malicious request. It inherits the identity and privileges of the victim to perform an undesired function on the victim's behalf.
It's perfectly valid to make an initial GET request on page load to get a fresh token and then submit it with the request performing an action.
If you want to confirm the identity of the person making the request you'll need authentication, which is a separate concern from CSRF.
My solution is as follows
Client [static html]
<script>
// Call script to GET Token and add to the form
fetch('https:/mysite/csrf.php')
.then(resp => resp.json())
.then(resp => {
if (resp.token) {
const csrf = document.createElement('input');
csrf.name = "csrf";
csrf.type = "hidden";
csrf.value = resp.token;
document.forms[0].appendChild(csrf);
}
});
</script>
The above can be modified to target a pre-existing csrf field. I use this to add to may pages with forms. The script assumes the first form on the page is the target so this would also need to be changed if required.
On the server to generate the CSRF (Using PHP : assumes > 7)
[CSRFTOKEN is defined in a config file. Example]
define('CSRFTOKEN','__csrftoken');
Server:
$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = $_SERVER['HTTP_REFERER'] ?? false;
// Check that script was called by page from same origin
// and generate token if valid. Save token in SESSION and
// return to client
$token = false;
if ($root_domain &&
$referrer &&
parse_url($referrer, PHP_URL_HOST) == $root_domain) {
$token = bin2hex(random_bytes(16));
$_SESSION[CSRFTOKEN] = $token;
}
header('Content-Type: application/json');
die(json_encode(['token' => $token]));
Finally in the code that processes the form
session_start();
// Included for clarity - this would typically be in a config
define('CSRFTOKEN', '__csrftoken');
$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);
// Check submission was from same origin
if ($root_domain !== $referrer) {
// Invalid attempt
die();
}
// Extract and validate token
$token = $_POST[CSRFTOKEN] ?? false;
$sessionToken = $_SESSION[CSRFTOKEN] ?? false;
if (!empty($token) && $token === $sessionToken) {
// Request is valid so process it
}
// Invalidate the token
$_SESSION[CSRFTOKEN] = false;
unset($_SESSION[CSRFTOKEN]);
There is very good explanation for same, Please check
https://cloudunder.io/blog/csrf-token/
from my understanding it seems static site won't face any issue with CSRF due to CORS restriction, if we have added X-Requested-With flag.
There is one more issue i would like to highlight here, How to protect your api which is getting called from Mobile app as well as Static site?
As api is publicly exposed and you want to make sure only allowed user's should be calling it.
There is some check we can add at our API service layer for same
1) For AJAX request(From Static site) check for requesting domain, so only allowed sites can access it
2) For Mobile request use HMAC token, read more here
http://googleweblight.com/i?u=http://www.9bitstudios.com/2013/07/hmac-rest-api-security/&hl=en-IN

Laravel 5.3 API route not saving session between requests

I am trying to build a static HTML viewer through Laravel's 5.3 API routing logic and JWT. The files are all stored on S3 and need to be protected so I thought the best way to do this was to make a kind of proxy that all the files pass through. That way I can check the token of the user from the API request and load the files accordingly.
The first file loads fine.
http://example.com/api/proxy/file.html?token={token}
The issue arises when the HTML file tries to load files from itself. It works when I strip out the authentication functions so I know it's not an issue with getting the files. It's because the token is not appended to future requests. It sends this instead without the token.
http://example.com/api/proxy/some_image.png
I attempted to add the following code to my token checker logic.
public function __construct(JWTAuth $jwtAuth)
{
$this->middleware(function ($request, $next) use ($jwtAuth) {
if (!$jwtAuth->getToken()) {
if (!Auth::user()) {
return response()->error('The token could not be parsed from the request', 400);
} else {
$this->authUser = Auth::user();
}
} else {
$this->authUser = $jwtAuth->parseToken()->authenticate();
Auth::setUser($this->authUser);
}
return $next($request);
});
}
But for some reason this does not work. When the first .html loads up with the token it tries to authenticate the user using Laravel's Auth middleware but Auth::user() returns null on the image request.

Using laravel socialite and jwt-auth without session

Short version: What would be the appropriate way to send the JWT generated from Facebook login (laravel/socialite) to the angularjs front end without using session.
Long Version
I am making an app that has angularjs front end and laravel 5.2 backend. I am using tymondesigns/jwt-auth for authentication instead of session.
I am also using laravel/socialite for social Facebook authentication. For that I am using the stateless feature of socialite so that I don't need session in any ways.
The basic authentication works perfectly. But, when I try to use Facebook login, I follow these steps
User clicks on a button on the angular side that redirects to the provider login page of the back end.
public function redirectToProvider() {
return Socialite::with('facebook')->stateless()->redirect();
}
2. User gives his login information. After logging in he is redirected to my handlecallback function.
try {
$provider = Socialite::with('facebook');
if ($request->has('code')) {
$user = $provider->stateless()->user();
}
} catch (Exception $e) {
return redirect('auth/facebook');
}
return $this->findOrCreateUser($user);
Next I use the findorcreate function to determine whether the user exists or not. If not than I just create a new user and create JWT from that.
$user = User::where('social_id', '=', $facebookUser->id)->first();
if (is_object($user)) {
$token = JWTAuth::fromUser($user);
return redirect()->to('http://localhost:9000/#/profile?' . 'token=' . $token);#angular
} else {
$result = array();
$result['name'] = $facebookUser->user['first_name']
$result['email'] = $facebookUser->user['email'];
$result['social_id'] = $facebookUser->id;
$result['avatar'] = $facebookUser->avatar;
$result['gender'] = $facebookUser->user['gender'];
$result['status'] = 'active';
$result['login_type'] = 'facebook';
$result['user_type'] = 'free_user';
try {
$user = User::create($result);
} catch (Exception $e) {
return response()->json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT);
}
$token = JWTAuth::fromUser($user);
return redirect()->to('http://localhost:9000/#/profile?' . 'token=' . $token);#angular
}
My problem is, in the last block of code I am having to send the jwt to my frontend via url. Which isn't secure at all. What would be the right way to send the generated JWT to the frontend without using session. Thank you
The official documentation of Laravel Socialite says:
Stateless Authentication
The stateless method may be used to disable session state verification. This is useful when adding social authentication to an API:
return Socialite::driver('google')->stateless()->user();
Then, you can authenticate using the jwt-auth method:
JWTAuth::fromUser($user)
If you're using $http on the Angular side, try returning the token as a JSON response from Laravel:
return response()->json(compact('token'));
Then store the token in localStorage or sessionStorage or what have you.
If you're generating your Angular page from within Laravel (i.e. not using Laravel as an API, but showing your Angular page from /public/index.php, for instance) you could load the view with the token in the data for the view.
As long as you're using HTTPS either of these two scenarios are better than passing the token in the redirect URL.
You can store token and use client side redirect without storing to browser history to redirect user to profile page without token in URL:
document.location.replace({profile-url})

Resources