Proper redirect to Vue SPA after successful Oauth with Laravel Passport (Implicit Grant Token) - laravel

I have a Laravel backend and a Vue SPA front end. Thus far, I have managed to et the Implicit Grant tokens working. My problem is about the redirection.
After successful authentication, Laravel redirects to http://localhost:8080/auth/callback#access_token=AUTH_TOKEN&token_type=TOKEN_TYPE&expires_in=EXPIRES_IN instead of http://localhost:8080/auth/callback?access_token=AUTH_TOKEN&token_type=TOKEN_TYPE&expires_in=EXPIRES_IN (Note the # and ?). I have to manually type in the correct URL after the successful authentication.
How do I ensure that it's redirected to the correct URL properly in the first place?

Based on this Stack Overflow answer, I managed to find a work around. This feature is not an error and redirecting to myurl.com? doesn't fix the problem. Here is a my solution. In my router index file, I have two entries.
The first is the redirect that the OAuth API returns the token to. The beforeEnter router method gets the URL string, replaces the # with a ? and the next method redirects the app to the save route.
The indexOf method gets the index of the ? in the newly formated url and passes it to the substring method which truncates the http(s):// part so that this solution is adaptable to both http and https.
The truncation results in ?access_token. This is then appended to the next method path entry and as a result, the prop can be accessed from the component at /save via the route.query object.
{
path: `/auth/callback`,
beforeEnter: (to, from, next) => {
let url = window.location.href
if(url.includes('#')) {
let newUrl = url.replace('#', '?');
next({path: `/save${newUrl.substring(newUrl.indexOf('?'))}`})
}
next();
},
},
{
path: `/save`,
component: Auth,
props: (route) => ({
access_token: route.query.access_token
})
},

Related

Remove token in frontend using Vue JS after deleting it from database in backend with Laravel?

I'm developing an application with Vue 3 and Laravel 9.
I did all login, registration and logout. However, I had an idea to keep the session unique per browser. I delete all tokens on user login if they exist. That way I can have front-end control with just one session open per browser, as I've already done this logic there.
The problem knowing the ways I can remove the token on the front-end to logout/redirect the user to login, after receiving the "Unauthenticated". For I check the vue routes with the token in localStorage.
Remembering, I'm using sanctum.
I'm looking for procedures. However, I'm afraid of doing something wrong, as I wonder about data security and vulnerabilities, as these processes of building a software require caution and a lot of testing.
I'm not sure if i got the question right, but you would normalle use an axios interceptor like so:
_axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
console.log('Intercept: 401 - Unauthenticated')
//DELETE THE TOKEN FROM STORAGE AND FROM AXIOS HEADER
delete window.axios.defaults.headers.common.Authorization
localStorage.removeItem('token')
return router.replace({ name: 'login' })
}
if (error.response.status === 403) {
console.log('Intercept: 403 - Unauthorized')
}
if (error.response.status === 422) {
console.log('Intercept: 422 - Validation Error')
}
return Promise.reject(error)
},
)

Axios AJAX call nulls parameter

I use Vuejs to create my frontend for my project.
At the creation of one component ('TimeCapsy.vue'), I make an AJAX call to my backend like this:
created: function () {
if (verify.verify_login()) {
let token = this.$cookies.get('jwt_us_cas');
let params = {'jwt': token};
console.log(params);
axios({
method: 'post',
url: dev.HOST+'getuserinfoobject',
params: queryString.stringify(params)
})
.then(response => {
console.log(response.data)
})
}
}
As you can see I use the
this.$cookies.get('jwt_us_cas');
to get the a json web token, that I set on the client at the login.
I use the queryString Library to stringify my parameters for my request.
I also tried it without the queryString.stringify(params) call, but I get the same error, e.g. the parameter still turns into null.
When I look at the console log, where I check the params variable, I get this output:
{jwt: "my token comes here"}
So I can see, that it gets the correct value from the cookie.
But when I check the answer from my backend (PHP), I get this error:
Undefined index: jwt in <b>D:\casb\public\index.php</b> on line <b>52</b>
Of course I know that it means, that jwt is null, but I can't understand why.
As I said, right before I make the call I check the params and it shows the token.
I checked the endpoint with Postman and the token as the jwt parameter and it returned a successfull call with the correct answer.
A correct answer is basically just a nested object with some information in it.
My PHP endpoint is pretty basic too:
Router::add('/getuserinfoobject', function () {
$response['response'] = User::getUserInfoObject($_POST['jwt']);
echo json_encode($response);
}, 'post');
So I guess that right before or in my call it nulls my parameter. But I can't understand how, since I make a lot of requests and never had this problem.
From axios docs
params are the URL parameters to be sent with the request
Which means, you should get the value with PHP $_GET.
Or $_REQUEST (which stores both $_GET, $_POST. Also $_COOKIE).
The other hand, you can use data key as docs says
data is the data to be sent as the request body
Only applicable for request methods PUT, POST, and PATCH
So the value would be available in $_POST
axios({
method: 'post',
url: dev.HOST+'getuserinfoobject',
data: {
jwt: token
}
})

Axios/XMLHttpRequest is sending GET instead of POST in production environment

I am running into a very strange issue. We are putting an app into production and one of the POST request is turning into a POST followed directly by a GET request to the same URL and the POST is never received in the backend (Laravel). In the chrome network tab it just looks like just a GET but with Burpsuite we can see the POST request.
The code responsible
async store() {
// This prints post
console.log(this.method());
await this.form[this.method()]('/api/admin/users/' + (this.isUpdate() ? this.id : ''));
if (!this.isUpdate()) {
this.form.reset();
}
},
The form.post method content
return new Promise((resolve, reject) => {
axios[requestType](url, this.data())
.then(response => {
this.busy = false;
this.onSuccess(response.data);
resolve(response.data);
})
.catch(error => {
this.busy = false;
if (error.response.status == 400) {
return this.displayErrors(error.response.data)
}
this.onFail(error.response.data.errors);
reject(error.response.data);
});
});
This question was also answered by me in the Larachat slack forum, and for others sake here is the answer for the next one with such a problem.
Just a little back story. In the chat we found out that it was receiving a 301 error which is a redirect error.
I had the same error recently when posting to a url on a staging server, it was working fine locally but not on the staging server.
The problem appeared to be a slash at the end of the post url.
So posting to https://example.com/post/to/ will not work.
Removing the / and posting to https://example.com/post/to will work.
Just for info, I had the same thing - axios request was being redirected. For me though, it turned out to be some localization middleware causing the redirect!
I set up an alternative route in the Api routes file (again Laravel as in the question), bypassing that middleware (probably where the route should have gone in the first place!). All good now! Schoolboy error I guess!
I confirm the previous answer. And I had the same problem from local to production ambience.
The call to an endpoint like / api / user / store / might be redirect to / api / user / store with a 301 status code and this call was interpreted like a GET that obviously we cant reach (because it not in our route list). So it don't work.
A solution can be to work on Apache configuration (trim last slash) but I prefer to adapt my Axios call.

Cannot make ajaxs call when deploy in heroku

I have an web app which I bundled using webpack, I placed my entire react/redux app in the public file which will be served by nodejs(express-generator). My app works when I run in localhost/ local env. However when I deploy to heroku. I cannot make calls.
The below is the error message:
bundle.js:19 GET https://glacial-cove-64389.herokuapp.com/users/ 401 (Unauthorized)
Object {err: Error: Request failed with status code 401 at e.exports (https://glacial-cove-64389.herokuapp.co…}
err
:
Error: Request failed with status code 401 at e.exports (https://glacial-cove-64389.herokuapp.com/bundle.js:19:10382) at e.exports (https://glacial-cove-64389.herokuapp.com/bundle.js:26:6821) at XMLHttpRequest._.(anonymous function) (https://glacial-cove-64389.herokuapp.com/bundle.js:19:9464)
__proto__
:
Object
initially I thought it could be my my ROOT_URL so I changed it the below is an example of my actions file.
const ROOT_URL = "//glacial-cove-64389.herokuapp.com"
const axiosOption = {headers: { authorization : localStorage.getItem('token')}}
/*Sign in user*/
export function signinUser({ email, password }){
return function(dispatch){
axios.post(`${ROOT_URL}/users/signin`, { email, password })
.then(function(res){
dispatch({ type: AUTH_USER })
localStorage.setItem('token', res.data.token);
browserHistory.push('/users');
})
.catch(function(err){
dispatch(authError('Invalid email or password'))
console.log({err});
})
}
}
So what happens is that the react recognize the login and push user to the correct route. but it return the above error msg status code 401 once it hits the main pages.
The main problem I have is when I try to perform CRUD which doesn't work
Here is my repo: https://github.com/boyboi86/API_basic_random
I found out the hard way..
If you intend to put everything within your public file when scaffold with express-generator. Putting CORS within your Nodejs is insufficient because now your axios (react) that makes the call is also subjected to CORS, And you will have to config within your axios with the following:
axios.defaults.headers.post['Access-Control-Allow-Methods'] = 'PATCH, DELETE, POST, GET, OPTIONS';
This is to ensure all calls made will be allowed. I realised this when I look at the response headers.

Django Angular Authentication CSRF cached template

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})

Resources