I have a backend API in Laravel with Sanctum, and separate repository SPA in NuxtJS
I am trying to authenticate my SPA with Sanctum. I am trying to get the CSRF cookie in the browser as per Sanctum documentation.
The problem is when I call the CSRF token endpoint provided by Sanctum, I get the correct response, but no cookie is set. Just like that, no errors. It doesn't matter if I am gonna use nuxt auth or just plain old axios call.
This is what I have:
DOMAINS: API - publisher.local:8080; frontend - publisher.local:3000
NUXT AUTH CONFIG
auth: {
strategies: {
laravelSanctum: {
provider: 'laravel/sanctum',
url: 'http://publisher.local:8080',
endpoints: {
login: { url: '/api/v1/login', method: 'post' },
// logout: { url: '/auth/logout', method: 'post' },
// user: { url: '/auth/user', method: 'get' }
}
},
},
},
AXIOS CONFIG
axios: {
baseURL: 'http://publisher.local:8080/api/v1', // Used as fallback if no runtime config is provided
credentials: true,
proxy: true,
},
sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,local:3000',
Sanctum::currentApplicationUrlWithPort()
))),
session.php
'domain' => env('SESSION_DOMAIN', '.local'),
I tried different combinations and variations of these settings and none of it works. Do you guys have any idea what could be wrong?
I figured this out I think. I got it to work.
So there are.local cannot be the top-level domain and that I think was perhaps part of the problem but I am not sure.
Changing domains to just pain old localhost did the trick but this solution had one issue. It will for some unknown to me reason I would automatically get an XSRF cookie on any call to my API, regardless of which endpoint I would call. Weird.
What worked perfectly was changing the domains to api.publisher.com and publisher.com, followed by all the settings from the Sanctum docs.
Just be super careful with the domains and make sure they match and that the settings are correct. It is super easy to reconfigure that thing and very hard to diagnose it!
Hope that helps!
Related
I'm trying to implement right now in my project a real time notification ui using laravel broadcast. I already made it work by broadcasting on a public channel but once I switched on to a private channel, the error POST http://localhost:8000/broadcasting/auth 404 (Not Found) appears when loading the page.
Here's what I made sure to check so far:
I've already uncommented both the App\Providers\BroadcastServiceProvider::class and Illuminate\Broadcasting\BroadcastServiceProvider::class, in the config\app.php,
I've also included Broadcast::routes(); and tested Broadcast::routes(['middleware' => 'auth:admin']); inside the boot() method of Providers\BroadcastServiceProvider if it'll work but still no dice,
I've also tried passing the Broadcast::routes(); in routes\web.php and,
Made sure that I have included <meta name="csrf-token" content="{{ csrf_token() }}"> in the main app.
The project that I'm working on implements SPA using Vue JS which is completely separated from the backend and is only connected through api. I hope someone could give me an insight with what going wrong with my methods. Thank you!
The question was asked a long time ago, but I hope it will be useful to someone. I had the same issue using stand-alone SPA (Vue.js) and REST API (Laravel). To simplify implementation of live comments system I used laravel-echo and pusher.js. To solve the issue I specified the auth endpoint according to the "Customizing The Authorization Endpoint chapter"
https://laravel.com/docs/8.x/broadcasting#customizing-the-authorization-endpoint
I used the following approach:
authEndpoint: process.env.VUE_APP_API_ROOT + '/broadcasting/auth'
where VUE_APP_API_ROOT is "http://api.yoursite.dev
But then I got a new issue with CORS. I used JWT authentication for API endpoints and a middleware from the https://github.com/fruitcake/laravel-cors package that allows to specify 'Access-Control-Allow-Origin' headers to solve the CORS issue when SPA sends requests to the API from a different domain.
So to solve the CORS and authentication issue I added the broadcast routes to api.php and set the JWT auth middleware
Broadcast::routes(['middleware' => ['auth.jwt:api']]);
and set a custom Pusher authorizer using Laravel echo
window.Echo = new Echo({
broadcaster: "pusher",
cluster: process.env.VUE_APP_PUSHER_APP_CLUSTER,
encrypted: true,
key: process.env.VUE_APP_PUSHER_APP_KEY,
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post(process.env.VUE_APP_API_ROOT + '/api/broadcasting/auth', {
socket_id: socketId,
channel_name: channel.name
})
.then(response => {
callback(false, response.data);
})
.catch(error => {
callback(true, error);
});
}
};
},
})
P.S. To provide axios with JWT token I used axios interceptors that allows to get token from Vuex.
axios.interceptors.request.use(config => {
const token = store.getters['auth/accessToken'];
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
Building an application with Laravel, Passport and Vue. This question does not directly pertain to logging in with oAuth2, but rather consuming the api that's protected by Passport with your own javascript code, as per the docs.
When accessing the home page and using axios to get /oauth, I get a error 401, as expected.
After logging in using the default login provided by laravel (uses web auth), I can go back to the home page, and the axios request for /oauth works great; for example /oauth/clients returns the clients of the logged in user, as expected.
mounted() {
//works as expected: 401 when logged out and response when logged in via /login
axios.get('/oauth/clients')
.then(response => {
console.log(response.data)
})
//Always returns 400 error
axios.get('/api/user')
.then(response => {
console.log(response.data)
})
}
However, when I try with axios to get /api/user, I get a 400 error, with the message Unauthenticated (regardless if before or after login, same error).
//Returns a 400, always
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
If I'm correct, the fact that the /oauth routes work proves that the laravel_token, csrf, and any such things are being sent correctly. Therefore, I think that this is a server side issue, especially with the auth:api guard. In the auth config file, I've set it to use Passport, and followed all the docs.
I'm confused as to why I get a 400 error and not a 401 when calling the api, and why it's not authenticating in the first place.
Especially frustrating since according to this video (11:30 mark), it's pretty much plug 'n' play.
Same behaviour with Postman.
Q: Any solutions to this error?
Full code on GitHub.
Add this code in your app/Http/Kernel.php
'web' => [
// Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
I'm currently building a laravel 5.4 powered page to manage users. I've done all basic pages such as home, login, register, dashboard using blade templating engine. Now I'm building the User Management page. I've successfully implemented VueJS for this particular page. All components are working perfectly.
Now the problem I'm facing now is using Axios to get logged in user data from API route. At first I'm using usual api route to get auth()->user() data but it doesn't work.
I've learned that I must use Laravel Passport to do this API operation.
These are the steps I made after that:
composer require laravel/passport
php artisan migrate
php artisan passport:install
Added the Laravel\Passport\HasApiTokens trait to your App\User model
Called the Passport::routes method within the boot method of your AuthServiceProvider
Set the driver option of the api authentication guard to passport
Added the CreateFreshApiToken middleware to your web middleware group
Edited bootstrap.js file like the following :
window.axios.defaults.headers.common = {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'X-Requested-With': 'XMLHttpRequest'
};
Axios Code :
axios.post('/api/getmydata', {
params: {
type: 'raw'
}
})
.then((response) => {
console.log(response);
}).catch((error) => {
console.log(error);
});
Changed route (api.php) :
Route::group(['middleware' => 'api'], function(){
Route::post('getmydata', 'ApiController#test');
});
Added function inside ApiController :
public function test() {
$user = Auth::user();
return $user;
}
The problem here is axios somehow return error: Unauthenticated
Is there anything wrong with my code?
Or is there any other way of achieving this? Thank you
Send the access token in the header of your API request:
Application Type :application/json
Authentication : Bearer [Access-Token]
My app is using React on the front end and Laravel 5.4 on the backend. I'm using fetch() to request data from the backend. The problem is that two sessions are created when the page loads. A TokenMismatchException is thrown by the CSRF Middleware when a POST request is made because the token that is sent matches the first session that is created, but it checks against the second.
I'm setting the token in app.blade.php
<meta name="_token" content="{{ csrf_token() }}">
And grabbing the token in the fetch config
fetchConfig = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
},
credentials: 'same-origin'
}}
Here are the decrypted sessions:
a:3:{s:6:"_token";s:40:"7obvOzPaqqJDtVdij8RaqrvmTFLjKA2qnvYMxry6";s:9:"_previous";a:1:{s:3:"url";s:24:"http://localhost/page";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
a:3:{s:6:"_token";s:40:"5Aiws9Qy72YzlkfWX81zkhzrSeiMDYjFWiLeDAwN";s:9:"_previous";a:1:{s:3:"url";s:41:"http://localhost/api/page";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
Request URL: http://localhost/page
API URL: http://localhost/api/page
How can I prevent a new session from being created when the React app makes its initial GET request?
Laravel automatically generates a CSRF "token" for each active user session managed by the application. This token is used to verify that the authenticated user is the one actually making the requests to the application. : https://laravel.com/docs/5.4/csrf
APIs are stateless. There is nothing like session in APIs. So you shouldn't use CSRF token in API. If you check Kernel.php of laravel. You will see Tylor didn't add VerifyCsrf middleware in API group. Which suggest that CSRF is only used in the request having session i.e, stateful request. I would recommend you to use JWT based authentication system for API. For more about JWT check here.
You can use this laravel package for JWT : https://github.com/tymondesigns/jwt-auth
I'm not sure what is your request URL and what is your target API url. Make sure both are on same domain(including subdomain).
I think its a good idea to disable CSRF validation only for API routes as these might be used by other domains, native apps etc
You can do that by adding following class file: app/Http/Middleware/VerifyCsrfToken.php
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'api/*',
];
}
Make sure to edit Kernal.php to point to the new class:
protected $middleware = [
'csrf' => 'App\Http\Middleware\VerifyCsrfToken'
];
To learn more how Laravel uses CSRF check this laracast video
I have controller method, Which returns json data and does not have security check logic.
def getJsonData(){
// return json
}
I am doing ajax request from a php page from another server(cross domain).
$.ajax({
type: "GET",
url: "http://localhost:8080/training/getJsonData",
data: { ListID: '1'},
dataType: "jsonp",
success: function(data) {
alert('Hi'+data);
$("#success").html(data);
}
});
Data is not coming from the server, It only works when the user is logged in to the app server. How to serve these requests without checking for login in grails.
Add IS_AUTHENTICATED_ANONYMOUSLY above your grails action, like
#Secured('IS_AUTHENTICATED_ANONYMOUSLY')
def getJsonData() {
....
}
EDIT...................................................................................
OK then may be you are using url mapping in config file(Simple Map in Config.groovy).
Change there like
grails.plugins.springsecurity.interceptUrlMap = [
...
'/training/getJsonData/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
...
]
That depends on how your application is configured with Spring Security.
If you are using the default configuration:
grails.plugins.springsecurity.securityConfigType = "Annotation"
You can either annotate the method for the action getJsonData with #Secured('IS_AUTHENTICATED_ANONYMOUSLY') or put a configuration in Config.groovy:
grails.plugins.springsecurity.controllerAnnotations.staticRules = [
'/training/getJsonData/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
]
Otherwise, if you are using the InterceptUrlMap:
grails.plugins.springsecurity.securityConfigType = "InterceptUrlMap"
You should configure an entry in your interceptUrlMap like this
grails.plugins.springsecurity.interceptUrlMap = [
'/training/getJsonData/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
// ...
]
Check out the appropriated section in the Spring Security plugin documentation.
Also, beware of using methods named getFoo in the controllers, they are called when the controller is created -- this is documented in the Gotchas page at Grails wiki. You should probably rename your method to avoid any problems.