I have such a route in my routes/web.php
Route::resource('/api/surveys', 'SurveyController');
As documentation says, it creates all needed routes for API. This is a function, that gets executed when I go for /api/surveys route:
public function index()
{
$request = request();
if(!$request->hasHeader('token')) {
return "No auth token found.";
}
$tokenCheck = $this->userService->isTokenValid($request->header('token'));
if($tokenCheck !== true) {
return $tokenCheck;
}
return $this->surveyService->all();
}
What it does, it checks if token header parameter is set, if not, it returns an error, if yes, it checks if token is valid and etc. if everything is OK, it should return surveys from database.
public function surveys() {
$request = \Request::create('/api/surveys', 'GET');
$request->headers->set('Accept', 'application/json');
$request->headers->set('token', \Cookie::get('token'));
$response = \Route::dispatch($request);
print_r('<pre>');
print_r($response);
print_r('</pre>');
}
I have a website, that should use that API I just created to get all survey records. I create a new request object, set header "token" with token I get from a cookie and then try to dispatch and get a response. But the problem is that everytime I get "No auth token found." error. That means $request->hasHeader('token') returns false, even tough I set it here in my request. If I print_r $request->all() in Restful controller, I get an empty array.
I tried Postman to access this API with token parameter, and it works fine in postman, but here, it seems that Request disappears while it travels to API controller.
What I did wrong here?
When you manually create a request and dispatch it, that works to get the routing to call the correct controller, however that does not affect the request that is bound in the container.
When your "fake" request is handled by the api controller, the request that it pulls out of the container is the original "real" request that was made by the user.
Instead of dispatching the route with your new request, you will need to app()->handle($request) the new request. This, however, will completely replace the original "real" request with your new "fake" request, so everything from the original request will be lost.
Having said all that, this method of consuming your own api is discouraged, even by Taylor. You can read his comment on this Github issue. So, consuming your own api like this may work, but you may also run into some other unforeseen issues.
The more appropriate solution would be to extract out the logic called by the api routes to another class, and then call that extracted logic from both your api routes and your web routes.
Related
I have middleware where I am assigning http headers to the request/response.
$response = $next($request)->header('x-robots-tag', 'noindex', false);
In the middleware, I can also apply this line after executing the above, to get the value I had just set...
echo $response->headers->get('x-robots-tag');
But, I want to access this outside of middleware but I'm not sure how to get the Response object back to achieve this.
How can I get the $response object from within my controller?
$response = \WHAT\GOES\HERE?;
echo $response->headers->get('x-robots-tag');
I can't seem to figure out what to put in the \WHAT\GOES\HERE part to access the response object again.
Update #1:
Still unresolved, but part of the problem appears to be that in order to add the header tags to the Response object within middleware requires $next($request) and the $next Closure causes the response processing to be done after the controller code has executed. So even though I'm not sure how to target the Response object from within the controller, it doesn't look like it would have the header tag assigned until afterward anyway.
I could set the headers directly in PHP in the middleware with
public function handle($request, Closure $next /*, $tags */)
{
$tags = array_except(func_get_args(), [0,1]);
if( count($tags) > 0){
header('x-robots-tag: ' . implode(', ', $tags));
}
return $next($request);
}
and then access it in the controller by pulling it out of the headers_list() but that's not ideal and working outside of the laravel ways...
For context, the idea was to assign middleware to routes and with the middleware assign the x-robots-tag response header with the desired attributes. noindex, nofollow, whatever... Then I was hoping to capture this and populate the equivalent meta tags accordingly using the data provided to the x-robots-tag. A two-birds with one stone sort of approach, but it has proven more difficult than I had expected.
So think of my application as a CMS (laravel 5.7). I'm slowly adding in more javascript to make it more reactive. So I had the usual validation logic that makes sure the user is logged in and all that. But now when I use Vue to submit a comment payload it looks a little like this:
So looking at this, anyone could just change/mock the this.user.id to any number, I would like to also send a login token with the payload which then gets validated in the backend once the server receives the post request.
In the backend, ideally I'd want to have some kind of safe guard that it checks whether the api_token of the user matches with this.user.id to ensure the user.id wasn't mocked on the front end.
I read this portion: https://laravel.com/docs/5.7/passport#consuming-your-api-with-javascript
Part of it says:
This Passport middleware will attach a laravel_token cookie to your outgoing responses. This cookie contains an encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. Now, you may make requests to your application's API without explicitly passing an access token:
But I'm still a bit unsure how that JWT gets generated in the first place. I don't have the vue components for the create token crud added because I want it to be done automatically. I think I'm slightly overthinking this..
Is this a good use case for Laravel Passport? I was looking through the tutorial and right now I don't have a need for custom oauth token creations and all the crud. I just want a unique token to be saved on the user side, that can expire, but also be used to validate requests. Am I on the right track here with Passport or should I use a different approach?
postComment(){
axios.post('/api/view/' + this.query.id+'/comment',{
id: this.user.id,
body: this.commentBox
})
.then((response) =>{
//Unshift places data to top of array, shifts everything else down.
this.comments.unshift(response.data);
this.commentBox = '';
document.getElementById("commentBox").value = "";
flash
('Comment posted successfully');
})
.catch((error) => {
console.log(error);
})
},
Update - Reply to Jeff
Hi! Thanks for your answer. It's not an SPA (might be in the future), but the comment box and the comment section is also integrated with websockets and there's a laravel Echo instance on it.
I guess where I'm feeling uncertain is the security of it.
I pass a user prop with :user="{{Auth::check() ? Auth::user()->toJson() : 'null'}}" into the vue component that contains the postComment() function.
This is where the id: this.user.id comes from. The route is defined in the api.php in a route middleware group for ['api'] like so:
Route::group(['middleware' => ['api']], function(){
Route::post('/view/{query}/comment','CommentController#store');
});
In my controller which calls a service to create the comment, the $request
public function makejson(createNewCommentRequest $request, Query $query){
$comment = $query->comments()->create([
'body' => $request->get('body'),
])->user()->associate(User::find($request->id));
$id = $comment->id;
$comment->save();
}
The createNewCommentRequest is a FormRequest class.
For now the authorize() function just checks whether the request()->id is an int:
public function authorize()
{
if(is_int(request()->id)){
return true;
}
return false;
}
From within there if I log the request(), all it outputs is:
array ( 'id' => 1, 'body' => 'gg', )
I thought I would need to add logic to authorize the request based on whether the user token and the request() yield the same user id? I'd want to avoid the scenario where someone can modify the post request and comment using another users id.
In the Network section of devtools, in the Request headers, i see it pushed a laravel_token cookie. I'm assuming that laravel_token is what stores the user session? If so, how would one validate based on that token?
I was playing around and added the route:
Route::get('/token', function() {
return Auth::user()->createToken('test');
});
When I went to it i got the following:
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImE4NDE2NGVkM2NkODc5NDY3MzAxYzUyNmVkN2MyMGViZTllNzJlMGMzMjRiMmExNWYzZDgwZGNmMzEzMDk1MTRmNTY1NGMxYWUwMTE2ZGRkIn0.eyJhdWQiOiIxIiwianRpIjoiYTg0MTY0ZWQzY2Q4Nzk0NjczMDFjNTI2ZWQ3YzIwZWJlOWU3MmUwYzMyNGIyYTE1ZjNkODBkY2YzMTMwOTUxNGY1NjU0YzFhZTAxMTZkZGQiLCJpYXQiOjE1NDY1NTQzNDEsIm5iZiI6MTU0NjU1NDM0MSwiZXhwIjoxNTc4MDkwMzQwLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.NMETCBkOrMQGUsXlcas6CvTFJ0xRC8v4AJzC5GtWANdl8YsPBGlyCozMe1OGc8Fnq8GC_GZFkKmMT27umeVcSyaWriZB139kvtWzY6ylZ300vfa5iI-4XC_tJKoyuwDEofqMLDA4nyrtMrp_9YGqPcg6ddR61BLqdvfr0y3Nm5WWkyMqBzjKV-HFyuR0PyPQbnLtQGCzRFUQWbV4XWvH2rDgeI71S6EwmjP7J1aDA2UBVprGqNXdTbxWpSINMkZcgrDvl4hdqNzet-OwB2lu2453R-xKiJkl8ezwEqkURwMj70G-t9NjQGIBInoZ-d3gM2C3J9mEWMB5lyfSMaKzhrsnObgEHcotORw6jWNsDgRUxIipJrSJJ0OLx29LHBjkZWIWIrtsMClCGtLXURBzkP-Oc-O9Xa38m8m6O9z-P8i6craikAIckv9YutmYHIXCAFQN2cAe2mmKp7ds1--HWN_P5qqw6ytuR268_MbexxGDTyq8KzUYRBjtkgVyhuVsS7lDgUHgXvJfHNmdCulpiPhmbtviPfWaZM19likSjKHLTpIn2PpfTflddfhB9Eb4X24wGH7Y5hwxASe7gDs_R707LphS1EH4cTE8p2XW_lLv0jo89ep9IUPUO27pWLsqabt8uTr5OoKQeNZmXT6XiJ9tK3HhRgvIt7DYt8vqlRw",
"token": {
"id": "a84164ed3cd879467301c526ed7c20ebe9e72e0c324b2a15f3d80dcf31309514f5654c1ae0116ddd",
"user_id": 1,
"client_id": 1,
"name": "lol",
"scopes": [],
"revoked": false,
"created_at": "2019-01-03 22:25:40",
"updated_at": "2019-01-03 22:25:40",
"expires_at": "2020-01-03 22:25:40"
}
}
Now in Postman, when I send a get request to:
Route::middleware('auth:api')->get('/user', function (Request $request){return $request->user();});
I added a authorization header of type Bearer Token for the string captured in the variable: accessToken. In return I get the user, no issue. However where and how is the accessToken generated? It's not saved in the database?
Take the user ID that Laravel gives you from the token, rather than sending it from the front end. You can also check the scopes assigned to the token:
Route::post('/api/view/{query}/comment', function (Request $request, Query $query) {
if ($request->user()->tokenCan('comment-on-queries')) {
$query->comments()->create([
'body' => $request->get('body'),
'user_id' => $request->user()->id,
]);
}
});
If this isn't a single page app, and only the comment box is handled by ajax, the default Laravel scaffolding should handle this by adding a CSRF token to axios config. In that case you don't need Passport, because the user is stored in the session. Still though, don't take the user ID from the front end, get it from \Auth::id()
Here's the key difference: If they login using PHP, your server has a session stored and knows who is logged in.
If you are creating a single-page app separate from your Laravel app, you have to rely on Passport and tokens to ensure the user has the authority to do what they're trying to do.
Figured it out, was overthinking it. Basically didn't need a whole lot to get it working.
Added the CreateFreshApiToken middleware to the web group in app\Http\Kernel.php.
The axios responses attach that cookie on the outgoing responses
The api middleware group had to be 'auth:api'.
The user instance can be then called via request()->user() which is awesome.
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.
I am working with a remote API that is normally accessed directly via JavaScript. In the normal flow, The user authenticates by sending Auth headers and in return is granted a cookie.
What I am trying to do is send auth headers from a laravel app, authenticate in the app controller, and provide API access through laravel controller functions.
I was hoping this would be as simple as authenticating and sending my subsequent API calls, hoping that the cookie given to the PHP server would continue to grant authentication.
Well that doesn't work and thats fine, but now I am thinking that I need to store my access cookie in the Session, and send it in the headers for future API calls.
Will this work/how can I go about this? My supervisors don't want to implement OAuth type tokens on the remote server and to me that seems like the best route, so I am a bit stuck.
Cookies cannot be shared across multiple hosts. The cookie (on the client) is only valid for path which set it.
EDIT - ADDING ADDITION AUTH DETAIL
Setting up remember me in Laravel
When migrating (creating) you User table add $table->rememberToken()
to create that column in your User table.
When user signs up to your service add a check box to allow them to
make the decision OR you can just set it true if you don’t to offer
the user the option as described in step 3
< input type="checkbox" name="remember" >
In your controller you add the following code:
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}
Users table must include the string remember_token column per 1. , now assuming you have added the token column to your User table you can pass a boolean value as the second argument to the attempt method, which will keep the user authenticated indefinitely, or until they manually logout. i.e. Auth::attempt([$creditentials], true);
Side note: the Illuminate\Contracts\Auth\UserProvider contract, public function updateRememberToken(Authenticatable $user, $token) uses the user’s UID and token stored in the User table to store the session auth.
AUTH ONCE:
Laravel has once method to log a user into the application for a single request. No sessions or cookies. Used with stateless API.
if (Auth::once($credentials)) {
//
}
OTHER NOTES
The remember cookie doesn't get unset automatically when user logs out. However using the cookie as I explained below in cookies example you could add this to your logout function in your controller just before you return the redirect response after logout.
public function logout() {
// your logout code e.g. notfications, DB updates, etc
// Get remember_me cookie name
$rememberCookie = Auth::getRecallerName();
// Forget the cookie
$forgetCookie = Cookie::forget($rememberCookie);
// return response (in the case of json / JS) or redirect below will work
return Redirect::to('/')->withCookie($forgetCookie);
OR you could q$ueue it up for later if you are elsewhere and cannot return a response immediately
Cookie::queue(forgetCookie);
}
Basic general cookie example that might help you. There are better approaches to do this using a Laravel Service provider
// cookie key
private $myCookieKey = 'myAppCookie';
// example of cookie value but can be any string
private $cookieValue = 'myCompany';
// inside of a controller or a protected abstract class in Controller,
// or setup in a service ... etc.
protected function cookieExample(Request $request)
{
// return true if cookie key
if ($request->has($this->myCookieKey)) {
$valueInsideOfCookie = Cookie::get($this->myCookieKey);
// do something with $valueInsideOfCookie
} else {
// queue a cookie with the next response
Cookie::queue($this->myCookieKey, $this->cookieValue);
}
}
public function exampleControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function one code
}
public function secondControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function two code
}
Not sure if SFDebug is any help in this situation. I am making an ajax post using jQuery. Which retrieves JSON data in my action URL and then makes a call to the Model method that executes the action. The part until my action URL, and the jQuery call to it work fine. With the data transmitted from the client to the server well received and no errors being made.
It is the part where it calls the method on the Model that is failing. My jQuery method looks like this:
$.post(url, jsonData, function(servermsg) { console.log(servermsg); }) ;
My server action is like this
public function executeMyAjaxRequest(sfWebRequest $request)
{
if($request->isXmlHttpRequest())
{
// process whatever
$servermsg = Doctrine_Core::getTable('table')->addDataToTable($dataArray);
return $this->renderText($servermsg);
}
return false;
}
The method of concern in the Table.class.php file looks like this:
public function addDataToTable($dataArray)
{
// process $dataArray and retrieve the necessary data
$data = new Data();
$data->field = $dataArray['field'];
.
.
.
$data->save();
return $data->id ;
}
The method fails up here in the model, when renderText in the action is returned and logged into the console, it returns the HTMl for SFDEBUG. Which indicates that it failed.
If this was not an Ajax call, I could debug it by seeing what the model method spat out, but this is a little tedious with Ajax in the mix.
Not looking for exact answers here, but more on how I can approach debugging ajax requests in a symfony environment, so if there are suggestions on how I can debug this, that would be great.
You must send cookie with session ide key via ajax
(Assuming you have XDEBUG configured on the server)
In order to trigger a debug session by an AJAX request you have to somehow make that request to send additional URL parameter XDEBUG_SESSION_START=1. For your example:
$.post(url + '?XDEBUG_SESSION_START=1', jsonData, function(servermsg) { console.log(servermsg); }) ;
You can also trigger it via cookie, but appending URL parameter usually easier.