So my problems is that the session token is generated.
and the token that i've sent via AJAX or AXIOS (cause im using vue and vue router for fetching API)
is getting a mismatch
This is the response i got when posting data
The ajax token is equal to the token in the meta tag of the main blade template
using this tag
Meta Tag in app.blade.php
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
Interceptor of Axios (purpose is to inject the csrf_token from the meta Tag)
Vue.axios.interceptors.request.use(function (config) {
config.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
console.log(config);
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Response:
array:1 [
"SessionToken" => "JfhmtCaTiQ49BtF2VK3TysvYnEQSE9n5i1uiHegO"
]
array:1 [
"AjaxToken" => "WqKOiaunbvJbxIsnEjetFoCm1mvdUYESRqfXO2lv"
]
VerifyCSRFToken middleware method:
protected function tokensMatch($request)
{
$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
dd(['SessionToken' => $sessionToken],['AjaxToken' => $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);
}
So i came up with this idea but its not working because its the token that im getting from the api is null or empty
Here is the method from my RegisterComponent.vue
submitForm() {
this.axios.get('/token')
.then(response => {
this._token = response.data
this.axios.post('/register',this.data)
.then(responseNew => {
console.log(responseNew.data);
})
.catch(responseNew => {
this.errors = responseNew.data;
})
});
}
as you can see im getting a token from my api.php in routes folder
and im also using the authentication api of Laravel and put it on the api routes too
Here is the api.php
Route::group(['middleware' => 'web'], function() {
Auth::routes();
});
Route::get('/token',function() {
dd(csrf_field());
});
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');
Route::resource('/users','UserController');
Route::group(['middleware' => 'auth'], function () {
Route::resource('/stores','StoreController');
Route::resource('/items','ItemController');
Route::resource('/transactions','StoreController');
Route::resource('/managers','ManagerController');
Route::resource('/employees','EmployeeController');
Route::resource('/customers','CustomerController');
Route::resource('/tags','TagController');
});
So how can i prevent it from generating that token that will cause mismatch?
Anyone answering this will surely help the authentication of my SPA ( Single Page App)
and its also giving me response status 302
You seem to have a bit misunderstanding. You have the csrf token configured for axios, so every request will have a header field containing the token, then you just need to make sure every request goes through laravel's csrf token validation function before it reaches your business logic, that's all you need to do to prevent csrf. The get('/token') before post('/register') seems unnecessary.
Also, talking about the /token route itself, csrf_field is not appropriate here, since it generates a hidden form field (another way to send csrf token apart from what we talked about earlier) to be embedded in a .php file like <form>...<?=csrf_field()?>...</form> => <form>...<input type="hidden" name="laravel_csrf_token" value="***">...</form>, which makes it meaningless to request csrf_field's result via xhr.
Related
I am trying out Laravel Sanctum and unfortunately I have a problem and hope you can help me.
I have several blade pages that I call up via the web route. For example '/', 'about-us' and /dashboard. The dashboard is only visible to users who are logged in.
web.php
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
In the dashboard, I have a tab where the logged-in user can view all other users. Here I send a get fetch call to the api route with Vanilla JS.
api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
My JS in the blade file looks like this:
async function send(method, url = "/api/users") {
const rawResponse = await fetch(url, {
method: method,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}, 2000);
const content = await rawResponse.json();
return content;
}
Problem: But I get a 401 here Object { message: "Unauthenticated." }. Why and what do I have to do to make it work?
My thinking so far.
As I understand it, Laravel sets a laravel_session and an XSRF-token cookie when it is called. If I fire the request against the webroute, laravel recognises me as the user. But if i fire the same request against the laravel api route i am not logged in for laravel. So I have to provide something in my request. I had tried to include the XSRF token in the request header. Like that:
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
// ...
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token
},
But that didn't work either.
Note: I have not configured anything else in Laravel to sanctum. I only ran the migration (personal_access_token). But the table is always empty.
My Kernel.php file:
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Everything working fine before I tried to use Laravel API authentication. And now some GET methods give MethodNotAllowedHttpException and say allowed method are POST,HEAD and for post methods it say allowed methods are GET,Head.
Here is my axios request in my Vue component
axios.post('api/save_post?api_token='+api_token, this.post)
.then(response => {
console.log(this.user);
this.success = response.data.message;
this.post.title=" ";
this.post.content=" ";
})
.catch(err=>{
this.error = err
//this.error = 'Error in saving post, please try again'
});
here is my route in routes/api.php
Route::middleware('auth:api')->post('save_post','Api\KnowledgeHubController#index')->name('savePost');
Included this in my welcome.blade.php file
meta name="csrf-token" content="{{ csrf_token() }}">
before meta there is < so that's not an error.
and Controller function is
public function index(Request $request)
{
$response = KnowledgeHub::create([
"title" => $request['title'],
"content" => $request['content'],
"author_id" => $request['author_id'],
]);
if($response)
{
return response()->json(['message'=>'Post published Successfully','response'=>$response],200);
}
return response()->json(['message'=>'Error in publishing post','response'=>$response],400);
}
some of Solutions I tried
1-included csrf token in header means in my main file(welcome.blade.php)
2-try to pass api_token in different ways to axios.post
3-run php artisan route:cache
Here is the result of php artisan route:list
POST | api/save_post | savePost | App\Http\Controllers\Api\KnowledgeHubController#index | api,auth:api
Can you call the route from outside your vue-application?
If you are 100% that everything is correct:
Try adding the following header to your axios-request:
const headers = {
'Content-Type': 'application/json'
}
axios.post('api/save_post?api_token='+api_token, this.post, headers)
.then(response => {
console.log(this.user);
this.success = response.data.message;
this.post.title=" ";
this.post.content=" ";
})
.catch(err=>{
this.error = err
//this.error = 'Error in saving post, please try again'
});
Still not working?
Try accessing the route with a program like POSTMAN
Clear Laravel Cache
php artisan cache:clear
php artisan route:cache
I resolved this issue but some other error exist.
In exception/handler.php i replace render function to handle error.
public function render($request, Exception $exception)
{
//return parent::render($request, $exception);
if($request->expectsJson())
{
return parent::render($request, $exception);
}else{
return response()->json($exception->getMessage(), 400 );
}
}
and in my router.js file when i remove mode:history
and in my web.php when i comment bellow code which were written to solve the refresh problem of vue component(means when i was refresh on browser it gave me 404 error.)
to solve that problem i used that code at the end of web.php file.
Route::get('{any}', function () {
return view('welcome');
})->where('any','.*');
But now that 404 error problem exist.
app.jss file
$('#modal-save').on('click', function(){
// geting the properties
$.ajax({
method:'POST',
url: url,
data: {body: $('#post-body').val(), postId: postId , _token: token}
})
// after done
.done(function(msg) {
$(postBodyElement).text(msg['new_body']);
$('#edit-modal').modal('hide')
});
});
my script in view code
<script>
var token = '{{ Session::token() }}';
var url = '{{ route('edit') }}';
</script>
Route file
Route::post('/edit', [
'uses'=>'PostController#postEditPost',
'as'=>'edit'
]);
My controller file
public function postEditPost( Request $request)
{
$this->validate($request,[
'body' => 'required'
]);
// checking auth user
$post = Post::find($request['postId']);
if(Auth::user != $post->user){
return redirect()->back();
}
// updating the post content
$post->body = $request['body'];
$post->update();
return response()->json(['new_body' => $post->body], 200);
}
Your'e sending an ajax request to the server, but dont accept it like so in the controller.
Try this:
// updating the post content
$post->body = $request['body'];
$post->update();
if(request()->expectsJson()){
return response()->json(['new_body' => $post->body], 200);
}
//here you can return to whereever you need it if its not ajax
return redirect('/');
Because of the VerifyCsrfToken middleware, you must provide CSRF token with each post, put, delete request.
Add meta tag inside head tag
<meta name="csrf-token" content="{{ csrf_token() }}">
Then use it in you ajax request
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
See X-CSRF-TOKEN
Trying to put together a solution to protecting a Laravel 5.4 api using OKTA and JWT. I have an SPA that logs into my application via OKTA and retrieves an access_token and id_token. It also passes this to API calls in the header using 'Authorization': Bearer ${accessToken} but now i am struggling to find a solution to verify this access token with OKTA within the Laravel backend. been looking at tymon/jwt-auth but cant workout how to add a custom solution to verifiy the token but i would assume it can be done using okta/jwt-verifier does anyone have any samples/guide? also looked at laravel/socialite and socialiteproviders/okta but that seems more about a traditional backend login rather than an SPA
Our okta/jwt-verifier library should be able to help you out here. You will have to create a custom middleware solution to capture and authorize the request based on the bearer token. Once you have that middleware set up, inside of the verifier library, you can run the following to verify the accessToken.
$jwtVerifier = (new \Okta\JwtVerifier\JwtVerifierBuilder())
->setAudience('api://default')
->setClientId('{clientId}')
->setIssuer('https://{yourOktaDomain}.com/oauth2/default')
->build();
$jwt = $jwtVerifier->verify($jwt);
By changing the client id and your okta domain above, you should be able to pass in the accessToken to the verify method. If you do not get any exceptions, you can assume that the jwt is valid and approve the request.
See the github repo readme for information about what you have access to once you verify the validity of the JWT
For those finding this post. In the SPA make sure you also define the issuer, this should be a useful start...
//react login
this.oktaAuth = new OktaAuth({
url: props.config.oktaUrl
,clientId:props.config.clientId
,redirectUri:props.config.redirectUri
,issuer: props.config.issuer
});
this.oktaAuth.signIn({
username: this.state.username,
password: this.state.password
})
.then((response) => {
if (response.status === 'SUCCESS') {
this.setState({
sessionToken: response.sessionToken
});
this.oktaAuth.token.getWithoutPrompt({
responseType: ['id_token', 'token']
,scopes: ['openid', 'email', 'profile']
,sessionToken: response.sessionToken
})
.then((tokenOrTokens) => {
this.setState({
tokenOrTokens: tokenOrTokens
});
window.localStorage.setItem('access_token', tokenOrTokens[1].accessToken);
})
.catch(function(err) {
console.log('err', err);
});
}
})
//api call
const accessToken = window.localStorage.getItem('access_token') || null;
const config = {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${accessToken}`
},
};
fetch(url, config)
.then((response) => {
...
//laravel api route
Route::group(['prefix' => 'restricted', 'middleware' => ['okta.validate']], function() {
Route::get('/getprotecteddata', 'MyController#getProtectedData');
});
//laravel kernel.php
protected $routeMiddleware = [
...
'okta.validate' => \App\Http\Middleware\ValidateOKTAToken::class,
];
//laravel middleware
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ValidateOKTAToken
{
public function handle($request, Closure $next)
{
$token = $this->parseAuthHeader($request);
$jwt = $this->validate($token);
\Log::info("ValidateOKTAToken jwt=" . json_encode($jwt->toJson()));
return $next($request);
}
protected function validate($token) {
$oktaClientId = env('OKTA_CLIENTID');
$oktaIssuer = env('OKTA_ISSUER');
$oktaAudience = env('OKTA_AUDIENCE');
$jwtVerifier = (new \Okta\JwtVerifier\JwtVerifierBuilder())
->setAudience($oktaAudience)
->setClientId($oktaClientId)
->setIssuer($oktaIssuer)
->build();
$jwt = $jwtVerifier->verify($token);
return $jwt;
}
protected function parseAuthHeader(Request $request, $header = 'authorization', $method = 'bearer')
{
$header = $request->headers->get($header);
if (! starts_with(strtolower($header), $method)) {
return false;
}
return trim(str_ireplace($method, '', $header));
}
}
I'm building a SPA with Vue. My front-end and my back-end (Laravel) are in the same codebase. I want to approach my API (that is in my back-end) via the Laravel Passport Middleware CreateFreshApiToken. I'm approaching my sign in method in my AuthController via web.php.
My problem:
As soon as I'm successfully signed in via my sign in method I would expect that at this time Passport created the laravel_token cookie. This is not the case. The cookie is created after a page refresh. But as I said I'm building a SPA and that's why I don't want to have page refreshes.
What I want:
I want to sign in via my sign in method then use the Passport CreateFreshApiToken middleware. After that I want to use the (just created in the middleware) laravel_token cookie so that I can correctly and safely speak to my own API in my signed-in section of the SPA.
More information:
Kernel.php
// Code...
protected $middlewareGroups = [
'web' => [
// other middlewares...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
];
// Code...
AuthController.php
// Code...
public function login()
{
if (Auth::attempt(['email' => Input::get('email'), 'password' => Input::get('password')], true)) {
return response()->json([
'user' => Auth::user(),
'authenticated' => auth()->check(),
]);
}
return response()->json(['authenticated' => false], 401);
}
// Code...
Login.vue
// Code...
methods: {
login: function (event) {
event.preventDefault();
this.$http.post(BASE_URL + '/login', {
email: this.email,
password: this.password,
})
.then(function (response) {
localStorage.user_id = response.body.user.id;
router.push({
name: 'home'
});
});
},
},
// Code...
What goes wrong? This:
CreateFreshApiToken.php
// Code...
public function handle($request, Closure $next, $guard = null)
{
$this->guard = $guard;
$response = $next($request);
// I'm signed in at this point
if ($this->shouldReceiveFreshToken($request, $response)) { // returns false unless you refresh the page. That's why it won't create the laravel_token cookie
$response->withCookie($this->cookieFactory->make(
$request->user($this->guard)->getKey(), $request->session()->token()
));
}
return $response;
}
protected function shouldReceiveFreshToken($request, $response)
{
// both methods below return false
return $this->requestShouldReceiveFreshToken($request) &&
$this->responseShouldReceiveFreshToken($response);
}
protected function requestShouldReceiveFreshToken($request)
{
// $request->isMethod('GET') - returns false because it's a POST request
// $request->user($this->guard) - returns true as expected
return $request->isMethod('GET') && $request->user($this->guard);
}
protected function responseShouldReceiveFreshToken($response)
{
// $response instanceof Response - returns false
// ! $this->alreadyContainsToken($response) - returns false as expected
return $response instanceof Response &&
! $this->alreadyContainsToken($response);
}
// Code...
I assume it is possible what I want to achieve right? If yes, how?
I had the same issue, decided to stick to client_secret way. I guess it's not relevant for you now, but I've found 2 ways of receiving the laravel token without refresh:
1) sending dummy get request with axios or $http, whatever you use - token will get attached to response;
2) changing requestShouldReceiveFreshToken method in CreateFreshApiToken.php - replace return $request->isMethod('GET') && $request->user($this->guard); with return ($request->isMethod('GET') || $request->isMethod('POST')) && $request->user($this->guard);
function consumeOwnApi($uri, $method = 'GET', $parameters = array())
{
$req = \Illuminate\Http\Request::create($uri, $method, $parameters, $_COOKIE);
$req->headers->set('X-CSRF-TOKEN', app('request')->session()->token());
return app()->handle($req)->getData();
}