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.
Related
How would you guys go about implementing something like this? I use Laravel as an API only. We have other framework where the login was implemented where it saves an httpOnly cookie (sessionId) after the user logs in. That's the main framework. We're migrating away from that old framework (Zend).
With the sessionId sent to Laravel from, say, a JS frontend, I'm able to lookup the current user based on the sessionId. That sessionId is then used to query the session database. I've created a middleware called "CheckForCurrentUser.php":
[..]
public function handle(Request $request, Closure $next)
{
// The reason for this is that the OPTIONS (request) does not include the cookie in the request.
$method = $request->method();
// SESSIONID is the name of the cookie created from the main framework
// once a user is logged in.
// SESSIONID is an exception in EncryptCookies.php
$sessionId = request()->cookie('SESSIONID');
if ($method === 'POST' && $sessionId !== '') {
// This function is only to get the logged in user id from the session database
$userId = $this->notImportantFunction($sessionId);
if ($userId) {
// User id found so make current user for this Laravel API
Auth::loginUsingId($userId);
} else {
// Instructs the frontend to let user log back in.
return abort(401);
}
}
return $next($request);
}
To recap, a user cannot authenticate from this Laravel application nor can I send an authentication token. They logged in from another framework. Laravel has access to the main framework databases.
This setup works. Using use Illuminate\Support\Facades\Auth; I use that as the "current user" in any controller/model.
Kernel.php looks something like:
[..]
protected $middleware = [
[..]
\App\Http\Middleware\CheckCurrentUser::class,
[..]
];
Before I go any further, is that how you'd implement something like this? It does work (ish) but I do not get the SESSIONID unless I check for a POST request.
I do not like this setup. I'm now using lighthouse and having issues using both the #auth and #inject directives. This is due to how I authenticate a use with Laravel so sorting how I authenticate a user should sort Lighthouse. Any tips on how to refactor this the right way? My routes are with /api/some-string
Lighthouse is not the issue. Should I send a authentication header, Lighthouse works. I believe Laravel does something behind the scenes with it sees a token in the header. I cannot send an authentication. I can only rely on the cookie. It's httpOnly so I have no access to that from JavaScript.
I am building a single-page application with Vue and laravel API as backend.
I've tried some packages like Vue Session and it worked well, however in almost every API call I always need to send 2 or 3 parameters that will always be the same (stored in vue-session), like user_id and company_id. I used to manage this with common PHP sessions (Zend_Session with Zend Framework) in my other applications, this way I always have that information stored in my backend session and don't need to send it every time by the frontend
Here's an example of how I used to do it with PHP session
$model->insert(array(
'user_id' => $this->session->user_id, //stored in session
'company_id' => $this->session->company_id, //stored in session
'field1' => $this->getParam('field1'), //frontend param (ajax)
'field2' => $this->getParam('field2') //frontend param (ajax)
));
And here's how I'm doing it with Vue and Laravel
const data = {
user_id: this.$session.get('user_id'), //this is what i'm trying to get rid of
company_id: this.$session.get('company_id'), //this is what i'm trying to get rid of²
field1: this.field1,
field2: this.field2
}
axios
.post('/api/some/endpoint', this.data)
.then(resp => {
//...//
})
Basically, in this example I want to not need to always send user_id and company_id as a post param.
Is there any way to get better code "reuse" in cases like this?
1, You can save your session data in the cookie. The browser will automatically send your cookie to the server. Don't forget to delete the cookie when the user logout.
2, If you still want to use Vue session or other storages, you can easily create a method that wraps your post method and add user's information to the payload
function postRequest(url, payload) {
payload.user_id = this.$session.get('user_id')
payload.company_id = this.$session.get('company_id')
return axios.post(url, payload)
}
Use this method whenever you want to make a post.
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.
I'm new to building APIs, and I'm using Laravel and some packages, like Passport.
In this project I'm creating an API to communicate with a mobile app and make some tasks, like creating users and adding some information related to the users.
I'm already getting the idea of how it works with tokens, and I already have almost everything done. My only question is, for example, before I create a register user to receive the token I have other information being presented in the mobile app, like news, and some listing information that is not needed to login or register.
In my API I already have these routes ready, but I cant access this information because I need a token.
How do iI handle this situation? When I need to access information, where I cant present it?
Use your controllers to retrieve the data you need from your database and then return a response with the data and create a token for the user:
...
$status = 200;
$response = [
'data' => Your data (user info, listings, news...),
'token' => Auth::user()->createToken("YourTokenName")->accessToken,
];
return response()->json($response, $status);
Then you could store that token in localStorage:
axios.post('your-api-route')
.then((response) => {
localStorage.setItem(`${"YourTokenName"}.jwt`, response.token);
...
})
And finally attach that token to the api routes that require authentication:
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem(`${YourTokenName}.jwt`);
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
}