Xero Api NodeJS - invalid grant issue - node.js

I am using the xero-node module to create an app for Xero with NodeJS.
For some reason every single request for a refresh token is coming back as invalid grant
i have been taken the code and made the callback attempt to grab the refresh straight after i do the auth so I can ensure thats its the latest token and still does the same thing.
Code is below this method is called when Xero passes back to the app (callbackURL)
The error i get is "invalid_grant" it does not give any other errors and there is no errors logs in Xero so very unhelful.
exports.callback = async function (req, res) {
const tokenSet = await xero.apiCallback(req.url);
try {
const newTokenSet2 = await xero.refreshWithRefreshToken('ClientID, 'ClientSecret', tokenSet.refresh_token);
}
catch(error){
console.log(`ERROR refresh: \n ${JSON.stringify(error.response.body, null, 2)}`);
};
///console.log(tokenSet);
};
Any ideas ?

Without seeing more of your code for context it's bit tough to tell. I'm assuming you're not actually sending 'ClientID' and 'ClientSecret' as strings.
I would refer you here for refreshing with a fully initialized client leveraging openid-client:
https://github.com/XeroAPI/xero-node-oauth2-app/blob/f1fbd3a08e840e54e8ce57f7050ddde6686208d8/src/app.ts#L233
Or here, to initialize an empty client and refresh by passing the client, secret, and refresh_token:
https://github.com/XeroAPI/xero-node-oauth2-app/blob/f1fbd3a08e840e54e8ce57f7050ddde6686208d8/src/app.ts#L236
I think what's happening is that you're using the second method but with an already initialized client. Again, tough to tell with limited context. If this doesn't resolve it please post more detail.

Related

Spotify JWT Flow

Tech stack: NodeJS backend Angular10 front end
I am implementing the Spotify API into my application. I'm having some issues with the flow.
To start off I have no issues authenticating or retrieving a token, but I am having an issue figuring out what to do with it. Assuming we understand the Spotify flow and are on the same page there is an issue presented for client-side rendered apps and that is how to get our JWT into the storage.
The reason this is an issue is because of the flow Spotify constrains us to, it requires a callback where the token is passed in as a query param.
This callback is on my backend NodeJS server, the endpoint is functioning fine but this issue starts here - I cannot pass in my user_id or any other user information it simply returns a JWT at this point the only thing I can do is send a template to the client but it's of a different domain.
Option (1)
Somehow use the Iframe hack to set a storage item in my other tab that's running my front end; I do not like this cause it's hacky and does not feel the right way to handle this.
Option (2)
Perform some kind of hack that maps user ids from the initial request and maps the correct user id to the correct token and then sending it to the client somehow ( database then GET ) I also don't like this it's hacky
What we don't want is to keep the client secret and other credentials on the front end like some examples I have seen, this is fine for personal stuff but not real apps for very obvious reasons.. All client secrets and passwords should be on the backend never sent to the client only their user JWT
So the issue here summarized, How do I return the JWT to the client without doing something hacky? the code below returns this to the client but brings us to option (1) above and obviously returns a template style which is undesirable cause I have a client-side rendered app!; I want something clean and professional, thank you.
router.get('/musicAuthCallback', function (req, res) {
const spotifyCode: string = req.query.code;
if (production.ROUTE_LOGGING) {
routeLogger.info('Spotify endpoint callback hit');
}
const _spotifyService = new SpotifyService();
_spotifyService.tradeSpotifyAuthToken(spotifyCode)
.then((jwt: any) => {
if (jwt === false) {
res.send({ "message": "Unable to get JWT for Spotify", "status": REQUEST_FAILED, "title": "Authentication Error" });
} else {
const template = `<h1> Success! </h1> <script>
var main=window.open("http://localhost:4200/");
main.setItem('spotify_jwt', ${jwt});
</script>`
res.send(template)
}
})
.catch((error) => {
logger.error(error);
res.send({ "message": "Something went really wrong..", "status": REQUEST_FAILED, "title": "Please Try Again.." });
});
})
This is silly but kind of illusive until you poke around the docs more.. the docs didnt make it clear to me I could use "state" as a query param.. in this i was able to pass the users ID into here so &state=600 and this is passed into the users callback URL which makes it possible to attach a user to a specific JWT given by Spotify
https://accounts.spotify.com/authorize?client_id=5fe01282e94241328a84e7c5cc169164&redirect_uri=http:%2F%2Fexample.com%2Fcallback&scope=user-read-private%20user-read-email&response_type=token&**state=123** <- this can be what you want! and can retrieve it in your callback URL sitting on your NodeJS server like this..
const spotifyCode: string = req.query.code; // jwt
const userID: string = req.query.state; // value you passed in

Use Firebase for Auth and MongoDB for DB

I'am building a react native mobile app with user authentication. I decided to use Firebase auth as it is relatively easy to implement.
I want to use my own backend (node, express) and my own DB (MongoDB) and I need to protect my API calls to check if the user is authenticated and if he has the permission to do the operation.
But I am not at ease with Token validation and I'am not sure I'am doing it the right way...
This is how I am trying to do to make a simple GET or POST request from the client (mobile app):
On the client I retrieve the current User token and send it in the header of my GET request:
firebase.auth().currentUser.getIdToken(true).then(token => {
fetch('http://localhost:3000', {
method: 'GET',
mode: 'cors',
headers: {
'AuthToken': token
}
}).then(res => res.json().then(res => console.log(res)))
.catch(err => console.log(err))
An on the server I use a middleware to check the token thanks to firebase admin
function checkAuth(req, res, next) {
if (req.headers.authtoken) {
admin.auth().verifyIdToken(req.headers.authtoken)
.then((token) => {
console.log(token)
next()
}).catch(() => {
res.status(403).send('Unauthorized')
});
} else {
res.status(403).send('Unauthorized')
}
}
So for a simple get request from a logged user I have to do the following :
get the user IdToken on client side
send it to the server
check it with firebase
do the operation on my DB
send the data back to the client
Am I doing it the right way ? I don't know if I should store the IdToken on the phone memory in order to avoid geting it from firebase everytime (but the token expires after 1 hour...)
It seems like a lot of "check from firebase" operation just for simple fetch...
Can you give me some advise on how to make all of this more efficient ?
Thanks a lot !
Overall, your flow looks fine to me, but there are a few things I'd optimize.
By passing true into getIdToken(true), you are forcing it to refresh the token each time before executing your fetch.
There is no need for that, as Firebase already automatically refreshes the ID token in the background. So you can make it less resource intensive, and quite a bit faster, with:
firebase.auth().currentUser.getIdToken().then(token => {
fetch('http://localhost:3000', {
...
After you call admin.auth().verifyIdToken(req.headers.authtoken), you get a result that also shows how long the token is valid for. So you could cache the verified token somewhere until it's close to that time, and use that cached version if you get the same input. That saves you on calls to verifyIdToken`.

Vue.js 2 Authentication JWT

[ Post has been edited: see below for answer ]
I am trying to make a Vue.js2 application using this boilerplate https://github.com/vuejs-templates/webpack
I am stuck on the Authentication process, using this library: https://github.com/websanova/vue-auth and attempting to use JWT as the authentication method. Never having rolled my own authentication before, I am slightly lost.
Packages I have installed which may be relevant: passport, passport-jwt, jsonwebtokens, passport-local, passport-local-mongoose
Looking at my logs I get a successful login response, but then it attempts to get /auth/user and responds with a 401 (unauthorized error). Perusing the auth library code, a GET req to /auth/user seems to be the expected behavior.
Here is the login code (client side):
methods: {
login() {
this.$auth.login({
body: this.data.body
success(res) {
console.log('Success: '+this.context);
this.localStorage.setItem('auth-token', res.body.token);
},
error(res) {
console.log("Error: " + this.context);
this.error = res.data;
},
rememberMe: true,
fetchUser: true
});
}
}
And here is the appropriate code server-side:
Removed Link | See Edits Section *
What I am sure of is this:
the server does in fact create a JWT which is valid (checked on jwt.io) during the login request. It does not appear to be set anywhere afterwards. It just sits there and then dies. There are mentions of an Authorization Bearer header in the response, which I am certain is not being set. Nor do I understand where or how to do this. There is no token set in localStorage after the login request. I'm not sure if this should exist, but think it likely that it should. In my console, searching local storage yields some strings and large integers, but no mention of a token in it.
Edits (8+ months later)
Gist to Solution here (slashes replaced by dashes in filenames):
https://gist.github.com/wcarron27/b0db7a16df9ceff924d4a75050093c55
The reason my login method originally did not work was that the localStorage token was not set correctly, and thus failed to pass the getData method on the client-side redirect. vue-auth does this by default. By editing the url it hits in the vue-auth config, I was able to direct it to the proper route(BUT only after I properly set the localstorage token. Use Vue.http.options.rootUrl (or something, it's in the main.js file in the gist) to set the Authorization header.
In the code, You must register vue-auth on the client side main.js, and call to it in the Login.vue "login" method. The client side config directs the http calls to the specified route in main.js. In the callback, the user and tokens are set in localStorage and the Vuex store.
The Http reqs go the the API side and hit the route in accounts.js. That route uses a passport strategy defined in ./util/passport.js, as well as a setJWT function defined in ./util/jwtLib.js.
After this, the client is redirected to a route of my choice, and data is populated by my store and ajax calls. Keep in mind, that while this should solve logins, i have not verified code correctness, as basically I would have needed to post the whole of two separate codebases.
Additionally, this does not account for refresh errors. State is dropped on refresh, so do not rely on vuex for persistence. A combination of localStorage and vuex does the trick, though.
I didn't verify this but, does remove the 'this' from your code on line 7 do the magic?
methods: {
login() {
this.$auth.login({
body: this.data.body
success(res) {
console.log('Success: '+this.context);
// original code here --> this.localStorage.setItem('auth-token', res.body.token);
localStorage.setItem('auth-token', res.body.token);
},
error(res) {
console.log("Error: " + this.context);
this.error = res.data;
},
rememberMe: true,
fetchUser: true
});
}
}

azure-mobile-apps-client - invokeApi Unauthorized

Setup:
Using the js library and following the client-side login/auth flow of my C# Xamarin projects.
CORS is setup and initial login is fine:
this.client = new WindowsAzure.MobileServiceClient(<endpoint>);
this.client.login(<provider>);
I store the returned "userId" and "mobileServiceAuthenticationToken" for future use.
Problem:
Calling the invokeApi of azure-mobile-apps-client:
this.client.invokeApi("myCustomerController", { method: 'GET' })
returns:
401 (Unauthorized)
Am I missing something? I'm expecting the client to be ready to make calls to my back-end services.
I've tried using the existing client directly after the login without success.
It also fails when setting the "userId" and "mobileServiceAuthentication token" of a new client.
The custom ApiController I'm calling is working fine when called from elsewhere (such as C# MobileServiceClient).
I assumed the login would get the client ready for use but not in my case. I had to call login, get the userId and token returned in the result, then set currentUser manually before make any future requests.
this.client = new WindowsAzure.MobileServiceClient("https://...");
this.client.currentUser = {
userId: "xxxxx",
mobileServiceAuthenticationToken: "xxxxx"}
The login method returns a promise. Can you confirm you are indeed waiting for the promise to be fulfilled before proceeding to do an invokeApi call?

How to handle expiration of OAuth access_token using Asana node client?

Using Asana's NodeJS module (https://github.com/Asana/node-asana) with OAuth, how should I handle expiration of the access_token? Does the client provide some mechanism that I should use to detect this? Does it provide something I should use to get a new access_token using the refresh_token? I haven't been able to find any discussion of the refresh_token in the documentation.
I've registered my app and I'm able to successfully get credentials using the Client.app.accessTokenFromCode API. Something like this:
function handleOauthCallback(req, res) {
var client = Asana.Client.create({
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
redirectUri: computeRedirectUrl(req)
});
client.app.accessTokenFromCode(req.query.code).then(function(credentials) {
// store credentials
}
}
I'm storing the entire credentials object that comes back from this call and later creating a client using those credentials. Something like this:
var client = Asana.Client.create({
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
redirectUri: computeRedirectUrl(req)
});
var storedCredentials = getStoredCredentials();
client.useOauth({ credentials : storedCredentials });
Now that I've got a client that's initialized with the credentials I got back from Asana (which includes an access_token and refresh_token), how should I handle expiration of the access_token? Do I need to check whether it's still valid myself and ask for a new token using the refresh token? Or will the client handle it for me automatically? If the client handles it, how do I find out when it gets a new access token?
Update
Reading the code, it appears that the client will try to use the refresh token if the access token is no longer valid. But I don't see any kind of notification that I can hook into to find out that there's a new access token. Is there a recommended strategy to handle this?
(I work at Asana). This is a great question, and we should add answers to the documentation.
The dispatcher can be passed an option called handleUnauthorized which is a callback to run when it gets a 401 (which if you started with good credentials should only happen when your token expires). It's documented in the code.
The default behavior of this option is to call Dispatcher.maybeReauthorize, which will make the backend request to get a new access token if it has a refresh token.
So if you just want the dispatcher to transparently refresh the access token, you don't need to do anything, it should "just work"! But if you want to intercept this process, you can pass handleUnauthorized in the dispatch options and then do whatever you want, possibly including calling the default method.
If you want to see the new access token, well .. the Authenticator class is abstract and we haven't provided a robust way to extract credentials from it; maybe we can add that. If you really need this, you could assume it's the oauth flavor with something like:
handleUnauthorized: function() {
return Dispatcher.maybeReauthorize.call(dispatcher).then(
function(reauthorized) {
if (reauthorized) {
onCredentials(dispatcher.authenticator.credentials);
}
return reauthorized;
});
}
This isn't beautiful, but it should work for you. Just be aware we may evolve this interface a bit over time and you may have to tweak it in the future.

Resources