Laravel/Sanctum user fetch problem, with auth-next - laravel

I'm trying to create an SPA for a laravel REST API protected by Sanctum
Right now, on my SPA the user can log in without a problem and the API sends back the token; but then the SPA doesn't fetch the user. I mean, it doesn't even try to fetch it; no error, no request, no nothing. Login and logout work flawlessly, but I'm unable to fetch the user.
Here's my config for auth module ( v5 ):
auth: {
strategies: {
laravelSanctum: {
provider: 'laravel/sanctum',
url: 'https://XXXXXXXXXXXXX/api',
token: {
property: 'access_token',
required: true,
type: 'Bearer'
},
endpoints: {
login: { url: '/login', method: 'post' },
logout: { url: '/logout', method: 'post' },
user: { url: '/user', method: 'get' }
},
user: {
autoFetch: true
}
}
},
My login function. If I understand correctly, just after the login the laravel/sanctum provider should fetch the user data:
async login() {
try {
let response = await this.$auth.loginWith('laravelSanctum', { data: this.form })
console.log('response -> ', response)
this.$router.push('/')
} catch (error) {
console.log('Error Response login -> ', error.response)
}
},
My logout function, just for completion ( it shouldn't have anything to do with the problem ):
async logout() {
try {
let response = await this.$auth.logout()
console.log('responselogout -> ', response)
this.$router.push('/login')
} catch (error) {
console.log('Error Response -> ', error.response)
}
},
Out of despair, I even created a function to try to fetch the user manually 😅 :
async fetch() {
try {
let responseuser = await this.$auth.fetchUser()
console.log('responseuser -> ', responseuser)
let loggedin = await this.$auth.loggedIn
console.log('loggedin -> ', loggedin)
} catch (erroruser) {
console.log('Error Response user -> ', erroruser.response)
}
},
On login, everything's fine but there is no request to the user endpoint:
Login request
When I try to fetch it manually, there is no request either:
Undefined response
And then on logout, everything works as it should:
Logout request
If it made the request to the /user endpoint ( either automatically after login, or manually when I use the fetch function ) and the API rejected it, or if there was an empty answer ... I would have something to work with ( I'm in control of the API too ), but with no request I just don't know where to start debugging the problem.
Any tip would be useful. Thanks in advance!

Just passing by to say I could finally solve the problem! Yay! 🥳
There was no request to /user because there was no XSRF-TOKEN cookie. And there was no XSRF-TOKEN because of browser security.
Long story short, this solution worked for me -> https://github.com/nuxt-community/auth-module/issues/1164#issuecomment-839199946
I hope this is helpful for anyone on the same situation :)

Related

Fitbit URL callback giving a response of NULL

I'm having trouble getting a response from a callback uri and I would really appreciate any help you could give me.
I am trying to use the Fitbit API which requires you to use a callback url to get an Auth Code.
Workflow:
1. Go to Fitbit url to get user to allow the app access to their personal data.
2. User agrees to the conditions
3. User gets redirected to my API
4. The API returns the code from (Code is located in URL and I can access it)
5. I console.log the code out to verify it
6. API returns the code
7. I work with code then exchanging it for an access token.
The problem is that I don't return the code (Or anything )when I return to the app even though I can console.log it on the API. The response I get is NULL
Here is the URL:
url = "https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://REDIRECT_URL&scope=activity%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight&expires_in=604800";
I then open the URL in the InAPPBrowser successfully:
if (url !== "") {
const canOpen = await Linking.canOpenURL(url)
if (canOpen) {
try {
const isAvailable = await InAppBrowser.isAvailable()
if (isAvailable) {
const result =InAppBrowser.open(url, {
// iOS Properties
dismissButtonStyle: 'done',
preferredBarTintColor: 'gray',
preferredControlTintColor: 'white',
// Android Properties
showTitle: true,
toolbarColor: '#6200EE',
secondaryToolbarColor: 'black',
enableDefaultShare: true,
}).then((result) => {
console.log("Response:",JSON.stringify(result))
Linking.getInitialURL().then(url => {
console.log("Tests: ",url)
this._setTracker(url as string);
});
})
} else Linking.openURL(url)
} catch (error) {
console.log("Error: ",error)
}
}
}
From here the URL opens successfully.
Here is the API now which is done in Typescript on AWS serverless and Lambda
export const handler: APIGatewayProxyHandler = async (event, _context, callback) =>{
let provider = event.path
//prints code
let x = event.queryStringParameters
console.log("Code: ",x)
const response = {
statusCode: 200,
body: "Success"
};
return response;
}
Please let me know if further detail is required?
Thank you!
Right so it turns out what I was doing was correct apart from the response should have been 301 which is a redirect response.
const response= {
statusCode: 301,
headers: {
"location": `app://CALLBACK RESPONSE ADDRESS?type=${provider}`
},
body: "Boom"
}

Vuejs Laravel Passport - what should I do if access token is expired?

I am using Vuejs SPA with Laravel API as backend. I successfully got the personal access token and store in localStorage and Vuex state like below.
token: localStorage.getItem('token') || '',
expiresAt: localStorage.getItem('expiresAt') || '',
I use the access token every time I send axios request to laravel api. Every thing works well. However, initially the token was set to 1 year expiration so when I develop I didn't care about token being expired and today suddenly I thought what is going to happen if token expired. So I set token expiry to 10 seconds in laravel AuthServiceProvier.php.
Passport::personalAccessTokensExpireIn(Carbon::now()->addSecond(10));
and then I logged in and after 10 seconds, every requests stopped working because the token was expired and got 401 unauthorised error.
In this case, how can I know if the token is expired? I would like to redirect the user to login page if token is expired when the user is using the website.
Be as user friendly as possible. Rather than waiting until the token expires, receiving a 401 error response, and then redirecting, set up a token verification check on the mounted hook of your main SPA instance and have it make a ajax call to e.g. /validatePersonalToken on the server, then do something like this in your routes or controller.
Route::get('/validatePersonalToken', function () {
return ['message' => 'is valid'];
})->middleware('auth:api');
This should return "error": "Unauthenticated" if the token is not valid. This way the user will be directed to authenticate before continuing to use the app and submitting data and then potentially losing work (like submitting a form) which is not very user friendly.
You could potentially do this on a component by component basis rather than the main instance by using a Vue Mixin. This would work better for very short lived tokens that might expire while the app is being used. Put the check in the mounted() hook of the mixin and then use that mixin in any component that makes api calls so that the check is run when that component is mounted. https://v2.vuejs.org/v2/guide/mixins.html
This is what I do. Axios will throw error if the response code is 4xx or 5xx, and then I add an if to check if response status is 401, then redirect to login page.
export default {
methods: {
loadData () {
axios
.request({
method: 'get',
url: 'https://mysite/api/route',
})
.then(response => {
// assign response.data to a variable
})
.catch(error => {
if (error.response.status === 401) {
this.$router.replace({name: 'login'})
}
})
}
}
}
But if you do it like this, you have to copy paste the catch on all axios call inside your programs.
The way I did it is to put the code above to a javascript files api.js, import the class to main.js, and assign it to Vue.prototype.$api
import api from './api'
Object.defineProperty(Vue.prototype, '$api', { value: api })
So that in my component, I just call the axios like this.
this.$api.GET(url, params)
.then(response => {
// do something
})
The error is handled on api.js.
This is my full api.js
import Vue from 'vue'
import axios from 'axios'
import router from '#/router'
let config = {
baseURL : process.env.VUE_APP_BASE_API,
timeout : 30000,
headers : {
Accept : 'application/json',
'Content-Type' : 'application/json',
},
}
const GET = (url, params) => REQUEST({ method: 'get', url, params })
const POST = (url, data) => REQUEST({ method: 'post', url, data })
const PUT = (url, data) => REQUEST({ method: 'put', url, data })
const PATCH = (url, data) => REQUEST({ method: 'patch', url, data })
const DELETE = url => REQUEST({ method: 'delete', url })
const REQUEST = conf => {
conf = { ...conf, ...config }
conf = setAccessTokenHeader(conf)
return new Promise((resolve, reject) => {
axios
.request(conf)
.then(response => {
resolve(response.data)
})
.catch(error => {
outputError(error)
reject(error)
})
})
}
function setAccessTokenHeader (config) {
const access_token = Vue.cookie.get('access_token')
if (access_token) {
config.headers.Authorization = 'Bearer ' + access_token
}
return config
}
/* https://github.com/axios/axios#handling-errors */
function outputError (error) {
if (error.response) {
/**
* The request was made and the server responded with a
* status code that falls out of the range of 2xx
*/
if (error.response.status === 401) {
router.replace({ name: 'login' })
return
}
else {
/* other response status such as 403, 404, 422, etc */
}
}
else if (error.request) {
/**
* The request was made but no response was received
* `error.request` is an instance of XMLHttpRequest in the browser
* and an instance of http.ClientRequest in node.js
*/
}
else {
/* Something happened in setting up the request that triggered an Error */
}
}
export default {
GET,
POST,
DELETE,
PUT,
PATCH,
REQUEST,
}
You could use an interceptor with axios. Catch the 401s and clear the local storage when you do then redirect user to appropriate page.

I can't use json to make a Post request to my web api using react

I created a webapi in ASP.NET Core, and I need to consume it using React, the web api works normally, if I use curl or postman among others, it works normally. The problem starts when I'm going to use React, when I try to make any requests for my API with js from the problem.
To complicate matters further, when I make the request for other APIs it works normally, this led me to believe that the problem was in my API, but as I said it works with others only with the react that it does not. I've tried it in many ways.
The API is running on an IIS on my local network
Attempted Ways
Using Ajax
$ .ajax ({
method: "POST",
url: 'http://192.168.0.19:5200/api/token',
beforeSend: function (xhr) {
xhr.setRequestHeader ("Content-type", "application / json");
},
date: {
name: 'name',
password: 'password'
},
success: function (message) {
console.log (message);
},
error: function (error) {
/ * if (error.responseJSON.modelState)
showValidationMessages (error.responseJSON.modelState); * /
console.log (error);
}
});
Using Fetch
const headers = new Headers ();
headers.append ('Content-Type', 'application / json');
const options = {
method: 'POST',
headers,
body: JSON.stringify (login),
mode: 'cors' // I tried with cors and no-cors
}
const request = new Request ('http://192.168.0.19:5200/api/token', options);
const response = await fetch (request);
const status = await response.status;
console.log (response); * /
// POST adds a random id to the object sent
fetch ('http://192.168.0.19:5200/api/token', {
method: 'POST',
body: JSON.stringify ({
name: 'name',
password: 'password'
}),
headers: {
"Content-type": "application / json; charset = UTF-8"
},
credentials: 'same-origin'
})
.then (response => response.json ())
.then (json => console.log (json))
Using Request
var request = new XMLHttpRequest ();
request.open ('POST', 'http://192.168.0.19:5200/api/token', true);
request.setRequestHeader ('Content-Type', 'application / json; charset = UTF-8');
request.send (login);
ERRORS
Console
Network tab
When I do this without being change the content type to JSON it works
because the API returns saying that it is not a valid type.
Apart from allowing CORS in you .NET configuration. You also need to return 200 OK for all OPTION requests.
Not sure how it's done in .NET but just create a middleware that detects the METHOD of the request, and if it's OPTIONS, the finish the request right there with 200 status.
Well I had the same issue and it seems that you need to add the action to the HttpPost attribute in the controller.
Here is an example.
[HttpPost("[action]")]
public void SubmitTransaction([FromBody] SubmitTransactionIn request)
{
Ok();
}
Try like this
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(option => option.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
app.UseAuthentication();
app.UseMvc();
}

JWT is not set in header

I'm following this tutorial, currently I can log in and out with a user but when a user logs in the JWT token isn't send with the header request (I think) so I get a 401 after the router.navigate. When I reload the page I can use the token and everything works.
In my login.component.ts I have this login function:
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result === true) {
// login successful
this.router.navigate(['home']);
} else {
// login failed
this.error = 'Username or password is incorrect';
this.loading = false;
}
}, error => {
this.loading = false;
this.error = error;
});
}
This calls the login function in the authentication.service.ts:
login(username: string, password: string): Observable<boolean> {
return this.http.post(this.authUrl, JSON.stringify({username: username, password: password}), {headers: this.headers})
.map((response: Response) => {
// login successful if there's a jwt token in the response
const token = response.json() && response.json().token;
if (token) {
// store username and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify({ username: username, token: token }));
// return true to indicate successful login
alert('Success');
return true;
} else {
// return false to indicate failed login
alert('Fail');
return false;
}
}).catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
If the login is successful the user is routed to /home:
this.router.navigate(['home']);
In the home.component.ts I have a getAll function that returns all movies in the database:
getAll() {
this._dataService
.getAll<Movie[]>()
.subscribe((data: any[]) => this.movies = data,
error => () => {
'something went wrong';
},
() => {
console.log(this.movies);
});
}
This function is called on the ngOnInit:
ngOnInit(): void {
this.getAll();
}
In my app.service.ts I have the get function:
public getAll<T>(): Observable<T[]> {
if (this.authenticationService.getToken()) {
console.log(this.authenticationService.getToken());
console.log(this.headers);
return this.http.get<T[]>('/api/movies/all', {headers: this.headers});
}
}
But when I log in I get this error after being routed to the home page:
GET http://localhost:4200/api/movies/all 401 (Unauthorized)
The problem (I think) is that when I get routed to the home page the header is missing the token. But as you can see from the console log the token is available in app.service.ts.
When I reload the page I do have the token set in the header and everything works:
Any ideas on how to expose the token to the header after the redirect?
//EDIT
For some reason I do get the JWT token when I set the header directly in the function:
return this.http.get<T[]>('/api/movies/all', {headers: new HttpHeaders().set('Authorization', 'Bearer ' + this.authenticationService.getToken())});
Instead of calling it like this:
headers = new HttpHeaders().set('Authorization', 'Bearer ' + this.authenticationService.getToken());
return this.http.get('/api/movies/' + id, {headers: this.headers});

How to use token refresh in ranm8/requestify?

I have my api with dingo/laravel. Normally works without problems for mobile (android).
My AuthController#token in dingo/laravel:
public function tokenRefresh()
{
$token = JWTAuth::getToken(); // Header:Auth..Baerer ...
if (!$token) {
throw new BadRequestHttpException('Token not provided');
}
try {
$token = JWTAuth::refresh($token);
} catch (TokenInvalidException $e) {
throw new AccessDeniedHttpException('The token is invalid');
}
return $this->response->withArray(['token' => $token]);
}
i making another app with nw.js, and i use it requestify module.
My example login request:
requestify.request(this.authUrl, {
method : 'POST',
body : {
email: document.getElementById('email').value,
password: document.getElementById('password').value
},
headers : {
'X-Forwarded-By': 'me'
},
dataType: 'json'
}).then(function (response) {
var body = response.getBody();
alert(body.token);
});
its request normally return valid token. its ok.
How about expires token?
What should I do?
Maybe, kind of like ajaxSetup for all request before.
I need to automatic refresh token when token expires.
What do you recommend?

Resources