I have an application which uses AJAX quite a bit on a Laravel 5.3 application. I have some pages that are behind authentication and some that are not. The ones that are inside of authentication are working fine. The one that is outside (public facing) are giving me a the infamous TokenMismatchException in VerifyCsrfToken.php line 68. In order to attach the token to the AJAX header, I am using this...
$.ajaxSetup({
cache: false,
async: true,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
And it is working because when I make a request, I can see this...
...but the tokens are not matching. When I go to the framework file Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class and do a dd() on the session token and the token that was passed, they do not match.
Things I have tried:
Clearing Cache (views, config, cache)
Changing session driver from
file to Redis
Using csrf_field() instead of AJAX headers
I cannot figure out why this is not working. Any other ideas?
If you look at this code of laravel Github Link
/**
* Determine if the session and input CSRF tokens match.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function tokensMatch($request)
{
$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
if (! is_string($sessionToken) || ! is_string($token)) {
return false;
}
return hash_equals($sessionToken, $token);
}
It checks for the X-CSRF-TOKEN and also tries to check for X-XSRF-TOKEN. You can also try to send the _token from the ajax. I hope this helps you.
And, I finally figured it out. I am using BrowserSync for livereload, which proxies all my requests to localhost:3000/*. When I was testing the public side, I was visiting it through the original domain name and not proxied through browsersync's localhost:3000 so that was causing session issues.
Basically, if you have BrowserSync running and you try in use your site not through browsersync, you can get token mismatch errors.
Related
How can I create a Laravel Gate where client has to provide CSRF _token in headers when when using my api?
Idea is that I want somekind of gate when using my route:
http://127.0.0.1:50004/api/third-party/unsplash
Otherwise, anyone can copy and use above route.
api.php
Route::get('/third-party/unsplash', [UnsplashController::class, 'show'])
**// my gate here!!!!**
UnsplashController.php
public function show()
{
return ['authorizationKey' => 'Client-ID 1234'];
}
Unsplash.vue file:
const myAsync = async function fetchUnsplash() {
const myAPIKey = await fetch(
'http://127.0.0.1:50004/api/third-party/unsplash'
);
const dataMyAPIKey = await myAPIKey.json();
const response = await fetch('https://api.unsplash.com', {
headers: {
Authorization: dataMyAPIKey,
},
});
console.log(response);
};
Rather than relying on a CSRF token, you probably want full API authentication with something like Laravel Sanctum. Implementing Sanctum will help you protect your API effectively (including from CSRF attacks).
If you really do want to require a CSRF token in the header, you can implement that by adding custom middleware to those routes that would check for the CSRF token. You can look at the VerifyCsrfToken middleware as a starting point. Typically, the X-CSRF-TOKEN header is used to pass the CSRF token in a request.
Finally, you could potentially just use the built-in VerifyCsrfToken middleware if you choose to convert your /third-party/unsplash route from a GET to a POST.
All-in-all, I think using a more robust solution like Sanctum will serve you well and prevent other potential issues that you might not have thought of yet.
Cookie is avaiable in browser but can not get it even can not check if it exists .
working on laravel and vuejs app where I am using passport for authentication.
I am consuming own api with vuejs so I have added CreateFreshApiToken middleware in kernel.php
vuejs is creating and sending laravel_token cookie successfully but I can not read it using vuejs or javascript.
since I can not check if cookie is available or not I can not check if user is logged in or not.
How I will tell vuejs that user is logged in and take to intended route ?
For now I am saving access_token in localStorage and sending it with each request but I am looking for a solution which can help to use laravel builtin laravel_token cookie to authenticate and authorize the users.
More Details
If i run this command in console then I get response
document.cookie.match('XSRF-TOKEN')
But
document.cookie.match('laravel_token')
returns null
I think the tick icon in the shown image at top is sign of making it private or something that makes it inaccessible.
Update
I found something from internet
But I still want to know how I can tell vuejs app that user is logged in or not ??
Update 2
For now I am sending access_token in header manually from vuejs and it is working fine.
if(localStorage.getItem('laravel_token') !== 'undefined' && !! localStorage.getItem('laravel_token') == true){
window.axios.defaults.headers.common['Authorization'] = 'Bearer '+ localStorage.getItem('laravel_token')
}
A little work around will be for you to implement the authenticated method in your LoginController, and return a json success code. You can then receive this code and store.
public function authenticated(Request $request, $user)
{
// return redirect()->intended($this->redirectPath());
return json_encode(['authenticated' => true]);
}
Using axios...
axios.post('/login', {
email: user.username,
password: user.password
}).then(response => {
.then(response => {
console.log(response)
//response.data.authenticated will return true if user has been authenticated
})
}).catch(err => {
console.log(err)
})
I'm not sure if I'm using the correct method for my problem.
I would like to get Infos via Axios. For example: calling: /api/user with Axios gets me the user, but I don't want to see the information when I go on domain.test/api/user.
I even want to use API calls to get Results of Functions even if the User is a Guest.
I installed everything there is on the Laravel Documentation for API, but still I'm not sure how the user gets the Token.
So if I call:
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
I get from the network tab {"message":"Unauthenticated."}. (I didn't forget to set: ...Middleware\CreateFreshApiToken::class and everything there is).
I think my problem is that I didn't register the User correctly. I got my two keys what should I do with it?
And then it's weird, reading the blog https://laravelcode.com/post/laravel-passport-create-rest-api-with-authentication, they use
$success['token'] = $user->createToken('MyApp')->accessToken;
But I don't get it. Where do I save it? I'm super confused, because every blog about shows Laravel Passport completely differently.
Or am I doing it wrong?
Passport is an OAuth2 server implementation which offers several different authorization strategies. While it is certainly possible to use Passport for authentication, it's primary purpose is authorization. This is done via token scopes. The type of token you choose to issue depends on your intended purpose with the API and whom your intended target is to be consuming your api.
Let's start with the following:
$success['token'] = $user->createToken('MyApp')->accessToken;
Here they are creating Personal Access Tokens. This is the simplest means of consuming your api, since clients are able to create the token themselves as needed and are long lived.
The Password Grant strategy is also a good option. A common approach when using this strategy is proxying authentications to Passport internally and merging the client id and secret into the request in a middleware.
public function handle($request, $next) {
return $next(tap($request, function($request) {
$request->merge([
'client_id' => config('services.passport.client.id'),
'client_secret' => config('services.passport.client.secret')
]);
// do any other pre-request work needed here
}));
}
Setup
To get started with Passport, run the command:
php artisan passport:install
This will create the encryption keys as well as a personal access and password grant client types, which will be stored in your database in the oauth_clients table.
Guard and Middleware
API routes need to be using the api guard, done by setting auth:api middleware on the route group(s) or Controller constructor.
// using via constructor
public function __construct()
{
$this->middleware('auth:api');
}
// via a route group
Route::group(['middleware' => ['auth:api'], function() {
// your api routes
});
Passport Routes
Your user model needs to be using the trait HasApiTokens and within the AuthServiceProvider call Passport::routes() in the boot method.
public function boot()
{
$this->registerPolicies();
Passport::routes();
// this is also an ideal time to set some token expiration values
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
}
To use the personal access token strategy for authentication, you need to create your own login and logout routes instead of using the builtin Laravel auth mechanisms.
Referring to the tutorial in your question, they have defined the following routes:
Route::post('login', 'API\PassportController#login');
Route::post('register', 'API\PassportController#register');
and the login method implemented as:
public function login(){
if(Auth::attempt(['email' => request('email'), 'password' => request('password')])){
$user = Auth::user();
$success['token'] = $user->createToken('MyApp')->accessToken;
return response()->json(['success' => $success], $this->successStatus);
}
else{
return response()->json(['error'=>'Unauthorised'], 401);
}
}
This is expecting a ajax request with an email and password to be posted to login and if successful it responds with an access token.
Storing/Using the Token
This token must be stored client side, either in a cookie or local storage, for example, and then added to every request from the client there after. You'll add the token to the Authorization header in client requests.
// Use a response interceptor to check for a token and put it in storage
axios.interceptors.response.use(response => {
// store the token client side (cookie, localStorage, etc)
if (response.data.token) {
localStorage.setItem('token', token)
}
return response
}, error => {
// Do something with response error
return Promise.reject(error);
});
// Use a request interceptor to add the Authorization header
axios.interceptors.request.use(config => {
// Get the token from storage (cookie, localStorage, etc.)
token = localStorage.getItem('token')
config.headers.common['Authorization'] = `Bearer ${token}`
return config;
}, error => {
// Do something with request error
return Promise.reject(error);
});
Accessing the Authenticated User
In order to get the user, pass the 'api' parameter to the auth() helper, Auth facade, or through the request:
$user = auth('api')->user();
$user = Auth::user('api');
$user = request()->user('api');
Keep in mind personal access tokens are long lived, so it's up to you to decide when and how they should expire.
Any http client can be used to make api requests, axios is a very common option and included with each Laravel installation.
I am building an API with Yii2 and have enabled the CORS filter to handle requests from a web frontend which is working.
However because of the pre-flight OPTIONS request and then the real POST request I am getting two records added to the database, one for each request. I would have thought that Yii should accept the OPTIONS request, return the correct headers and then exit. Why does it actually process the full request?
I am working around this for now by adding this to the top of the controller action:
if(Yii::$app->request->getMethod() == 'OPTIONS') {
return;
}
Is that the best approach or am I missing something?
That should be wrong because a browser need the options response to know the allowed list of verbs he can send. Otherwise a 401 error may be raised. Its source code can be seen here:
class OptionsAction extends \yii\base\Action
{
public $collectionOptions = ['GET', 'POST', 'HEAD', 'OPTIONS'];
public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
public function run($id = null)
{
if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') {
Yii::$app->getResponse()->setStatusCode(405);
}
$options = $id === null ? $this->collectionOptions : $this->resourceOptions;
Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $options));
}
}
And that is all what it does: sending a list of allowed verbs within a response headers.
Maybe the POST request has been sent twice from client script due to unexpected responses. Try to apply the answer I posted in your other question instead. I think it will also solve this:
Yii2 CORS with Auth not working for non CRUD actions.
I am getting status code 403 when I try to log in after successfully being logged in and logged out.
Client side is written in Angular and server side is in Django.
This goes as follows:
Client requests url '/' fetches main HTML template with all required static files ( angular, bootstrap, jQuery sources and angular sources defined by me) with
<div ng-view></div> tag into which further templates will be inserted.
Via $location service is redirected to url '/#/login'
This rule from $routeProvider is executed once '/#/login' is hit:
$routeProvider.when('/login', {
templateUrl: 'login.html'
});
'login.html' is served by django view and form for logging in is rendered to the user
User logs in successfully providing proper credentials
Then user logs out, by clicking on a button, which fires '$http.get(
'/logout/'
);' and then is redirected to url '/#/login'
Here is the problem. When user fills in credential form and sends 'POST' request, 403 is returned. I thought that it is, because this routing is done only by angular and since 'login.html' template has already been requested it has been catched and can be served without hitting backend, but after logging out currently possesed CSRF cookie is stale, so that's why I am getting 403. So I tried to remove that template:
logout: function(){
var forceLoginTemplateRequest = function(){
if( $templateCache.get('login.html') !== 'undefined'){
$templateCache.remove('login.html');
}
};
var responsePromise = $http.get(
urls.logout
);
responsePromise.success(forceLoginTemplateRequest);
return responsePromise;
}
After doing that I could see client side requesting 'login.html' template always after logging out, so I thought I could provide CSRF cookie when serving that template from backend:
#urls.py
urlpatterns = patterns(
'',
...
url(r'^$', serve_home_template),
url(r'^login.html$', serve_login_template),
url(r'^login/', login_view, name='login'),
url(r'^logout/', logout_view, name='logout'),
...
)
#views.py
#ensure_csrf_cookie
def serve_login_template(request):
return render(request, "login.html")
#ensure_csrf_cookie
def serve_home_template(request):
return render(request, 'home.html')
But still it doesn't work and I am getting 403 when trying to log in after logging out. The only way I managed it to work is to simply refresh the page so that every single file, whether template or source file is requested again from the backend and CSRF cookie is updated with them.
Here is my app's run section for making sure CSRF cookie is sent with every request:
mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService', '$templateCache',
function($http, $cookies, $location, $rootScope, AuthService, $templateCache) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
if ( !(AuthService.isLoggedIn() == "true")){
$location.path('/login');
}
});
}]);
This could be a cache problem. Try to add the never_cache decorator to all your views:
from django.views.decorators.cache import never_cache
...
#ensure_csrf_cookie
#never_cache
def serve_login_template(request):
return render(request, "login.html")
...
I solved this problem by setting X-CSRFTOKEN header in $routeChangeStart event.
I don't exactly know how module.run phase works, but it seems that when certain event defined within it occurs everything what is defined outside this event's handler body isn't executed.
mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService',
function($http, $cookies, $location, $rootScope, AuthService) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
// Added this line
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
if ( !(AuthService.isLoggedIn() == "true")){
$location.path('/login');
}
});
}]);
This works together with removing 'login.html' template from $templateCache.
Instead of removing templates on client side with $templateCache service it is also possible to set your server to serve templates and set following headers:
Cache-Control: no-cache, no-store, must-revalidate
Pragma : no-cache
Expires : 0
Another way of dealing with this problem is to simply force page refresh, however I don't like this approach, since this is not pro-single-page-app approach. :)
One solution could be to read the current, fresh csrftoken directly from the cookie and then update the stale cookie using javascript.
var fresh_token = document.cookie.match('csrftoken=([a-zA-Z0-9]{32})