Not getting refresh_token when authenticating with paypal API - paypal

Node.js
Using paypal sandbox env
const PAYPAL_BASE_URL = "https://api.sandbox.paypal.com";
const PAYPAL_TOKEN_URL = `${PAYPAL_BASE_URL}/v1/oauth2/token`;
const tokenOptions = {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Credentials": true,
},
data: qs.stringify({ grant_type: "client_credentials" }),
auth: {
username: `${CLIENT_ID}`,
password: `${CLIENT_SECRET}`,
},
url: `${PAYPAL_TOKEN_URL}`,
};
async function getToken() {
return await axios(tokenOptions)
.then((res) => {
console.log(res.data.access_token);
console.log(res.data.expires_in);
console.log(res.data);
return res.data;
})
.catch((err) => {
console.log(err);
});
}
Response
"scope": "https://uri.paypal.com/services/invoicing https://uri.paypal.com/services/disputes/read-buyer https://uri.paypal.com/services/payments/realtimepayment https://uri.paypal.com/services/disputes/update-seller https://uri.paypal.com/services/payments/payment/authcapture openid https://uri.paypal.com/services/disputes/read-seller https://uri.paypal.com/services/payments/refund https://api.paypal.com/v1/vault/credit-card https://api.paypal.com/v1/payments/.* https://uri.paypal.com/payments/payouts https://api.paypal.com/v1/vault/credit-card/.* https://uri.paypal.com/services/subscriptions https://uri.paypal.com/services/applications/webhooks",
"access_token": "xxxxxxxx",
"token_type": "Bearer",
"app_id": "xxxx",
"expires_in": 32346,
"nonce": "2020-11-07T20:09:09Zmc3xM34owS0WsAI5rHVx2eOJb80xJ06Z6tFQx6LT_i0"
As you can see I am not getting the refresh_token. Reading the documentation I wanted to use the refresh token to get a new access_token when the expire time is getting close.
Should I not be getting a refresh_token here?

A client credentials request using a clientid/secret does not give a refresh_token.
Just repeat the same request when needed.

#Preston PHX
Adding a comment here as the comments does not allow that many characters.
Can I do something to get a refresh_token?
Problems I have is that I am using node as a proxy with http-proxy-middleware.
app.use("/", PROXY_TO_PAYPAL);
On app startup I authenticate and store the token in a variable that will expire.
console.log(`Starting proxy at ${HOST}:${PORT}`);
console.log(`PAYPAL_TOKEN_URL: ${PAYPAL_TOKEN_URL}`);
// set the token on startup
getToken().then((data) => {
token = data.access_token;
console.log(`expires_in ${parseInt(data.expires_in)} seconds`);
const timeoutDate = getTimeoutDate(parseInt(data.expires_in) * 1000);
tokenTimeOut = timeoutDate.getUTCMilliseconds();
console.log(`Token times out at ${timeoutDate.toUTCString()}`);
});
});
I need to update the token BEFORE it expires, otherwise I risk that a proxy request will fail as it could potentially use an expired token.
I created a scheduler that will watch the when the token is getting close to expire and will renew it (call the authentication again), like say 15 min before.
async function getToken() {
return await axios(tokenOptions)
.then((res) => {
console.log(res.data.access_token);
console.log(res.data.expires_in);
console.log(res.data);
return res.data;
})
.catch((err) => {
console.log(err);
});
}
cron.schedule("* * * * *", function () {
console.log("running a task every minute");
// refresh token
if (tokenTimeOut - new Date().getUTCMilliseconds() < 900*1000) {
getToken().then((data) => {
token = data.access_token;
const timeoutDate = getTimeoutDate(parseInt(data.expires_in) * 1000);
tokenTimeOut = timeoutDate.getUTCMilliseconds();
console.log(`Token times out at ${timeoutDate.toUTCString()}`);
});
}
});
I am adding the Bearer header on each request bore it is proxied
const middlewareOptions = {
target: `${PAYPAL_BASE_URL}`, // target host
changeOrigin: true,
onProxyReq(proxyReq, req, res) {
if (!token) {
getToken().then((data) => {
token = data.access_token;
tokenTimeOut = data.expires_in;
});
}
proxyReq.setHeader("Authorization", `Bearer ${token}`);
},
};
I might get the same token again until I am closer to the expire date (I think) - which could work. Eventually I would get a new token and a new expire time.
I am confused about the documentation. Here they talk about the refresh_token https://developer.paypal.com/docs/connect-with-paypal/integrate/#6-get-access-token
But here there are not refresh_token
https://developer.paypal.com/docs/api/get-an-access-token-curl/
What I wanted to do is when I am close to the expire time I would use the refresh_token to get a new token.

Related

How could i pass cookies in Axios

I am in a next-js app and my auth token is stored in cookies.
For some raisons i use Swr and Api route to fetch my secured api backend.
i am trying to find a way to put my auth token in all api request.
During login cookie is set
res.setHeader(
'Set-Cookie',
cookie.serialize('token', data.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: data.expires_in, // 1 week
sameSite: 'strict',
path: '/',
}),
);
This is an example of a page using swr fetch
//page/test.ts - example of my test route
const { data, error } = useFetchContent(id);
if (error) {
showError('error');
replace('/');
}
return <DisplayContent content={data} />
This is a swrFetchHook
// fetchContentHook
function useFetchContent(id: string): ContentDetail {
return useSWR<any>(`/api/content/${id}`, fetcherApiRoute);
}
const fetcherApiRoute = (url: string): Promise<any> => {
return axios(url)
.then((r) => r.data)
.catch((err) => {
console.info('error is ', err)
throw err
});
};
export default useFetchContent;
inside api route
export default async (req, res): Promise<ContentDetail> => {
const { id } = req.query;
if (req.method === 'GET') {
const fetchRealApi = await apiAxios(url);
if(fetchRealApi) {
// here depending on result of fetchRealApi i add some other fetch ...
return res.status(200).json({ ...fetchRealApi, complement: comp1 });
}
return res.status(500)
}
return res.status(500).json({ message: 'Unsupported method only GET is allowed' });
};
and finally api axios configuration
const apiAxios = axios.create({
baseURL: '/myBase',
});
apiAxios.interceptors.request.use(
async (req) => {
// HERE i am trying to get token from cookies
// and also HERE if token is expired i am trying to refresh token
config.headers.Authorization = token;
req.headers['Content-type'] = 'application/x-www-form-urlencoded';
return req;
},
(error) => {
return Promise.reject(error);
},
);
export default apiAxios;
I am stuck here because i cant find token during apiAxios.interceptors.request.use...
Did you know what i am doing wrong, and am i on a correct way to handle this behavior ?
To allow sending server cookie to every subsequent request, you need to set withCredentials to true. here is the code.
const apiAxios = axios.create({
baseURL: '/myBase',
withCredentials: true,
});
Nilesh's answer is right if your API is able to authorize requests based on cookies. Also it needs the API to be in the same domain as your frontend app. If you need to send tokens to the API (the one which is in the cookie), then you will need a small backend component often called BFF or Token Handler. It can extract the token from the cookie and put in an Authorization header.
At Curity we've created a sample implementation of such a Token Handler, of which you can inspire: https://github.com/curityio/kong-bff-plugin/ You can also have a look at an overview article of the Token Handler pattern.

axios interceptor: need to undestand the javascript code

I am trying to understand this code. And also how to use it
https://stackoverflow.com/a/53294310/2897115
createAxiosResponseInterceptor() {
const interceptor = axios.interceptors.response.use(
response => response,
error => {
// Reject promise if usual error
if (errorResponse.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
axios.interceptors.response.eject(interceptor); <---- What does this do
return axios.post('/api/refresh_token', {
'refresh_token': this._getToken('refresh_token')
}).then(response => {
saveToken();
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return axios(error.response.config); <--- what does this do
}).catch(error => {
destroyToken();
this.router.push('/login');
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
);
}
Generally i use axios script with access_token is as:
const url = "dj-rest-auth/password/change/";
const auth = {
headers: {
Authorization: "Bearer " + localStorage.getItem("access_token"),
Accept: "application/json",
"Content-Type": "application/json",
},
};
const data = {
old_password: old_password,
new_password1: new_password1,
new_password2: new_password2,
};
const promise = axios.post(url, data, auth);
promise
.then((res) => {
console.log(res)
})
.catch((err) => {
if (err.response) {
console.log(`${err.response.status} :: ${err.response.statusText}`)
console.log(err.response.data)
}
})
And in this code how to use the interceptor
Eject interceptor
axios.interceptors.response.eject(interceptor); <---- What does this do
Internally, interceptors.response is an array of interceptors, the method axios.interceptors.response.use return the id of the new interceptor. Calling eject passing the id of the interceptor will set the corresponding item in the array to null, and the interceptor has no effect anymore.
When we receive the response code 401, we use the interceptor to send another request to get the token. To avoid the infinity loop if the latter also receives the response code 401, we eject the interceptor in this case.
Resend original request
return axios(error.response.config); <--- what does this do
After receiving the token, we want to resend the original request, its configuration is stored in error.response.config according to the response schema
To use the function, call it before sending the request. (People talk about it in the thread of the accepted answer.)

Securing REST API with JWT access_token and refresh token approach

I'm building a web application using the MERN stack. I've made a REST API to interact with my front-end the usual way,but I'm not able to figure out the best way to build a secure API and overall security of the application.
So far this is what I've tried
Flow of authenticating a user/request :
1. User enters the credentials
2. Server responds with access_token and refresh_token, caching refresh_token in redis cache with email as key and access_token in localStorage
3. To access a protected route, user sends the request with Bearer {ACCESS_TOKEN} in request header
4. Server checks if the token is valid and not expired, serving the user back with protected resource
5. In case, the access_token is expired (i've kept the token expiry short eg. 5 min), an action is fired from the front-end (using redux actions), a sample request :
POST http://localhost:3000/user/token
Header Bearer {ACCESS_TOKEN}
Body
{
"email" : "user#example.com"
}
Server performs a check in redis cache if the email has refresh_token
If yes, server responds with updated access_token
This is how I'm signing the tokens
const signToken = user => {
let email = user.email;
let name = user.name;
const accessToken = JWT.sign({
iss : process.env.APP_NAME,
sub : user.id,
email : email,
name : name
}, JWT_SECRET, {
expiresIn : 300
});
const refreshToken = JWT.sign({
iss : process.env.APP_NAME,
sub : user.id,
email : email,
name : name
}, REFRESH_TOKEN_SECRET, {
expiresIn : '30d'
});
// Save to redis cache
REDIS_CLIENT.set(email, refreshToken);
return {accessToken, refreshToken};
}
and for getting new access_token
// Refresh token
const refreshToken = (req, res, next) => {
const extractToken = req.headers['authorization'];
const token = extractToken.split(' ')[1];
if (!token) {
return res.status(403).json({
message : 'Unauthorized'
});
}
REDIS_CLIENT.get(req.body.email, (err, cachedToken) => {
if (err) return res.status(500).json({message : 'Something went wrong'});
if (cachedToken == null) {
return res.status(403).json({message : 'Unauthroized'});
} else {
JWT.verify(cachedToken, REFRESH_TOKEN_SECRET, (err, rt) => {
if (err) {
return res.status(500).json({message : 'Something went wrong'})
} else {
// Generate a new access token here
const newAccessToken = JWT.sign({
iss : process.env.APP_NAME,
sub : rt.sub,
email : rt.email,
name : rt.name
}, JWT_SECRET, {
expiresIn : 300
});
return res.json({
"accessToken" : newAccessToken
});
}
});
}
});
}
Is this approach secure? If not, what are some good ways to design the authentication flow if stack is MERN. Upon searching the internet, many have advised to set short expiry in access_token and use the refresh_token to generate new access_token, but I haven't found how to logout user on inactivity? What if attacker gets the expired token and email id, and generates the new access_token? How will be the oAuth integration?
PS : Sorry for putting so much here, came here after a lot of searches on the internet but found nothing much

Axios + Ionic React + Sails: _csrf token 403 Forbidden

I am developing an API with Sails.js and an user App with Ionic-React. At page load I make an axios request to get the _csrf token. When I submit the data from a login form to sails I always get a 403 Forbidden response. I disabled csrf (config/security.js) in sails and then I could retrieve the response. I am sending the token in the header.
I am trying too to get the session cookie but its not working I think that might be why the server refuses the request.
Ionic App:
componentDidMount(this: this) {
axios.get('http://localhost:1337/api/v1/security/grant-csrf-token')
.then(response => {
const _csrf = response.data._csrf
this.setState({
form: {
...this.state.form,
_csrf: _csrf,
}})
});
}
OnSubmit:
const { emailAddress, password, _csrf } = this.state.form;
const config= {
data: {
"emailAddress": emailAddress,
"password": password,
},
headers: {
"x-csrf-token": _csrf
},
withCredentials: true,
jar:cookieJar,
};
axios.post('http://localhost:1337/api/v1/users/authenticate', null, config)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})};
On Chrome DevTools Network Response:
On Postman this same request works and I get a 200 with the user data, and the request does include the sails.sid cookie.
I do not want to disable csrf protection, that wouldn't be a solution. Is it the sails.sid cookie that I am missing?
I am using this,
axios({
method: 'post',
crossdomain: true,
url: apiFormUrl,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
"Authorization": access_token
}
})
.then
and it works

Automatic request signing with API Gateway REST API and Amplify

This https://aws-amplify.github.io/docs/js/api#signing-request-with-iam says AWS Amplify provides the ability to sign requests automatically ..is this the same with API gateway REST requests that are restricted by Cognito?
auth.currentSession().then(token => {
console.log('>>>>', token.getIdToken().getJwtToken());
authToken = token.getIdToken().getJwtToken();
const myInit = { // OPTIONAL
headers: {
Authorization: authToken
},
response: true,
};
api.get(apiName, path, myInit).then(response => {
// Add your code here
console.log(response);
}).catch(error => {
console.log(error.response);
});
}
);
but I get Authorization header requires 'Credential' parameter. Authorization header requires 'Signature'
But in angular this does not work as Auth.currentSession() does not compile
endpoints: [
{
name: 'test',
endpoint: 'https://xyz.execute-api.us-west-2.amazonaws.com/test',
custom_header: async () => {
// Alternatively, with Cognito User Pools use this:
return {Authorization: (await Auth.currentSession()).idToken.jwtToken};
}
}
]
}
Resolved had typo with the request url it had to be /items/:test where test was the partition name in dynamo, also the
headers: {
Authorization: token
},
is not required:
https://github.com/aws-amplify/amplify-js/issues/2810#issuecomment-470213459