I am setting up Stripe Connect as explained here (Standalone Account). I handle the authorization and the retrieval of the access_token on my node server.
The user can visit the link MY_SERVER_URI/authorize and will be redirected to a pre-defined stripe AUTHORIZE_URI:
app.get("/authorize", function(req, res) {
// Redirect to Stripe /oauth/authorize endpoint
res.redirect(AUTHORIZE_URI + "?" + qs.stringify({
response_type: "code",
scope: "read_write",
client_id: CLIENT_ID
}));
});
After the user authorizes Stripe Connect, he or she will be redirected to a pre-defined REDIRECT_URI, which in this case equals to MY_SERVER_URI/oauth/callback, where the following script is executed:
app.get("/oauth/callback", function(req, res) {
var code = req.query.code;
// Make /oauth/token endpoint POST request
request.post({
url: TOKEN_URI,
form: {
grant_type: "authorization_code",
client_id: CLIENT_ID,
code: code,
client_secret: API_KEY
}
}, function(err, r, body) {
var accessToken = JSON.parse(body).access_token;
// Do something with your accessToken
// For demo"s sake, output in response:
res.send({ "Your Token": accessToken });
});
});
Now everything here works fine and the application is able to get the accessToken. However, this accessToken needs to be saved and matched with the user who is granting the access from the client side.
My question therefore boils down to, how can I either pass a client-side parameter (like the client-side userId) in the oauth/callback GET request, or process the server handling on the client side (e.g. a $http GET request instead of visiting the uri)? I guess that the later is not the recommended option.
I made two attempts:
I tried to pass a parameter using a dynamic REDIRECT_URI, but the
problem is that Stripe requires that alle urls need to be specified
first (resulting that no parameters can be passed in the redirect
url).
I tried to access the MY_STRIPE_URI/authorize with a $http GET request, but this gave me the obvious error No 'Access-Control-Allow-Origin' header is present on the requested resource
What can be done?
You have to pass your user id as "state" parameter and Stripe will return it on the callback. The only way I found to avoid session
Generally your scenario is as follows:
Make request to some route on your server and store the user's id there: req.session.user = {id: '...'}
From that route redirect the user to the third party authorization URL
In the route where you receive the access token, store it in the session as well: req.session.user.access_token = '...'
Use that access token for subsequent requests to the Stripe's API
Note:
Don't try to hack the authorization_code OAuth flow
You may find Grant easier to use for that type of OAuth flow, Stripe is supported
Relevant comment
Related
What Is Happening
My Webhook from shopify is not passing the details to my SuiteScript 2.0 Suitelet in NetSuite.
What do I want to happen
I want shopify to send the JSON object to my netsuite Suitelet so I can process the order in NetSuite.
Details
I am trying to make a connection between shopify and Netsuite using Shopify's webhooks.
I have set up a webhook as follows
The URL for my webhook is;
https://XXXXXXX-sb1.extforms.netsuite.com/app/site/hosting/scriptlet.nl?script=XXX&deploy=XX&compid=XXXXXXX_SB1&h=XXXXXXXXXXXXXXXXXXX&caller=ecommerce&key=XXXX-XXXX-XXXX-XXXX
This link calls a Suitelet which when I personally paste the link in the URL is works. However when I click "Send Test Notification" I do not see any evidencethat the Suitelet has executed. The first line of the suitelet is;
log.debug("Running");
I have changed the Webhooks URL to instead go to RequestBin and sure enough the webhook works.
WHAT HAVE I TRED
I have removed the extra query string parameters "caller" and "key"
from the URL. Does not solve the problem.
I have confirmed the Webhook works when changing the URL to RequestBin.
One frustrating limitation with public Suitelets is that they require the User-Agent header to claim to be a browser. (See for example SuiteAnswer #38695).
I had the same issue as you with a BigCommerce webhook, and what I ended up doing was proxy the webhook through a simple Google Cloud Function that modified the user agent.
const request = require('request');
exports.webhook = (req, res) => {
request.post(
{
url: process.env.NETSUITE_SUITELET_URL,
body: req.body,
json: true,
headers: {
'User-Agent': 'Mozilla/5',
Authorization: req.headers['authorization'],
},
},
function(error, response, body) {
res.send(body);
}
);
};
What combination of requests and responses are needed to get an Oauth token from eBay? What is a runame and what headers do I need to keep eBay happy?
After three frustrating days of trying to get Ebay's oauth to give me an access token, I have finally worked it out. As the docs are pain and there is little to no help online, I have decided to post my solution here in the hope that it will help others. I am no good at StackOverflow so let me know if I need to improve my formatting.
app.get("/login/ebay", (req, res) => {
res.redirect(`https://auth.sandbox.ebay.com/oauth2/authorize?client_id=DeanSchm-TestApp-SBX-b843acc90-fd663cbb&redirect_uri=Dean_Schmid-DeanSchm-TestAp-kqmgc&response_type=code`
);
});
The first thing you need to do is redirect to this URL.
The format is like this
https://auth.sandbox.ebay.com/oauth2/authorize?client_id=&redirect_uri=&response_type=code
There is also a scope property, but I don't understand that yet, and I got back a token without is so me.
That URL takes you to the eBay login page. If you are using the sandbox, you need to create a sandbox user and login with sandbox credentials.
Once you log in, eBay will redirect you to a URL of your choosing. You enter the URL you want to be redirected to here.
It's in the ebay developer section under Get A Token From Ebay Via your Application.
This URL can be anything. you just have to handle it in node or express or whatever, because as soon as someone signs in that URL is where they are heading.
Here is how I handled it
app.get("/auth/ebay/callback", (req, res) => {
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization:
"Basic " +
btoa(
`client public key:client secret keys`
)
},
data: qs.stringify({
grant_type: "authorization_code",
// parsed from redirect URI after returning from eBay,
code: req.query.code,
// this is set in your dev account, also called RuName
redirect_uri: "Dean_Schmid-DeanSchm-TestAp-kqmgc"
})
})
.then(response => console.log(response))
.catch(err => console.log(err));
});
A few gotchas that got me.
Make sure you have space after "Basic " in the authorisation
header.
bota is a 3rd party library that base 64 encodes your public and
secret keys. There are many ways to do this. I just did it this way because I stole a bunch of code.
With Axios, the request body is called data but with fetch and other
methods it might be called something else like body or param
The Axios method is in a get request because of the redirect from ebay
defaults to an http get.
ebay now uses https. Make sure you are using
sandbox URLs
We also had to use JS for the eBay API and solved your mention problem with developing a new Lib. It's available here. This lib will also automatically try to refresh the token if it's expires.
This is how we obtain the oAuth token:
import eBayApi from 'ebay-api';
const eBay = new eBayApi({
appId: '-- or Client ID --',
certId: '-- or Client Secret',
sandbox: false,
siteId: eBayApi.SiteId.EBAY_US,
ruName: '-- eBay Redirect URL name --' //in this case: Dean_Schmid-DeanSchm-TestAp-kqmgc
});
// This will generate the URL you need to visit
const url = eBay.oAuth2.generateAuthUrl();
// After grant access, eBay will redirect you to RuName page and set the ?code query.
// Grab the ?code and get the token with:
eBay.oAuth2.getToken(code).then((token) => {
console.log('Token', token);
ebay.oAuth2.setCredentials(token);
// Now you can make request to eBay API:
eBay.buy.browse.getItem('v1|382282567190|651094235351')
.then(item => {
console.log(JSON.stringify(item, null, 2));
})
.catch(e => {
console.log(e);
});
});
Another example with scope can we found here.
Some hints:
with "scope" you tell eBay what you plan to use. You can find the
Descriptions here, under Sandbox/Production Keys Box. (OAuth
Scopes)
if you use axios you can use the auth config, so you dont't
need btoa:
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
// ...
auth: {
username: 'appId',
password: 'certId'
}
});
To use sandbox without https, e.g. localhost, you can setup a redirect on a https site and redirec/pass the code to non-https site.
I'm trying to load an album from Google Photos via javascript but I don't understand how the api works, I started reading Google Photos API but no luck. Is there a code reference that I can follow to get a list of the photos of my album?
I found this but doesn't work
<script>
var scopeApi = ['https://www.googleapis.com/auth/photoslibrary', 'https://www.googleapis.com/auth/photoslibrary.readonly', 'https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata'];
function onAuthPhotoApiLoad() {
window.gapi.auth.authorize(
{
'apiKey': 'MY_API_KEY',
'client_id': "MY_CLIEND_ID",
'scope': scopeApi,
'immediate': false
},
handlePhotoApiAuthResult);
}
function handlePhotoApiAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
GetAllPhotoGoogleApi();
}
}
function GetAllPhotoGoogleApi() {
gapi.client.request({
'path': 'https://photoslibrary.googleapis.com/v1/albums',
'method': 'POST'
}).then(function (response) {
console.log(response);
}, function (reason) {
console.log(reason);
});
}
onAuthPhotoApiLoad();
While in the process of developing a Photos synching script, I spent a few days researching and testing the Oauth 2.0 documentation. It's a lot to take in, but hopefully this Cliff-notes version is helpful:
App Setup You first need to get an application configuration through the developer console at console.developers.google.com/ and make sure that the Photos data is shared.
You'll get a JSON file that looks like this
{"installed":{
"client_id":"xxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"project_id":"xxxx-xxxxxxxx-123456",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"xxxxxxxxxxxxxxxxxxxxxxxx",
"redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]
}}
Request Authorization Code - You then need to write code that uses those values to get an authorization token - basically a string that indicates the user has allowed your application access to their data.
Send a request to the auth_uri endpoint with these values in the querystring:
scope - a space-delimited list of scopes from developers.google.com/photos that says you want your user to grant access to these features
redirect_uri - a URL you own that can capture an incoming querystring
client_id - from your developer config in step 1
state - 32 random bytes, base64 encoded and made URL-friendly by replacing "+","/","=" with "-","_","" respectively
code_challenge - a SHA256 hash of another 32 random bytes, base64 encoded and made URL-friendly
code_challenge_method - "S256" (no quotes)
Authorization round trip Sending this composed URI to a user's browser will allow them to choose a Google account and show which scopes are being requested. Once that form is submitted, it will redirect to your redirect_uri with querystring (Method = GET) values:
code - the authorization code you can use to request an access token
state - a string you can use to validate against your hash
Get an access_token Finally you exchange the authorization code for an OAuth AccessToken that you'll put in the HTTP header of all the API requests. The request goes to the token_uri from step 1 and has these request body (Method = POST) parameters:
code - you got from the redirect querystring in Step 3
redirect_uri - same as above, but this may not be used
client_id - from configuration
code_verifier - code_challenge before it was hashed
client_secret - from configuration
scope - can be empty here
grant_type - "authorization_code" (no quotes)
Use the access tokens The response from that request will have an access_token and a refresh_token. You can use the short-lived access_token immediately in your API request's HTTP header. Store the long-lived refresh_token so you can get a new access_token without authorizing again.
That's the gist of it. You can look at my Powershell script for an example of the authorization and authentication flows which work even though the rest is a little buggy and incomplete. Paging through albums is getting a 401 error sometimes.
I am trying to implement a way of only allowing authorized admins to access pages with sensitive information within my Web App/API. I am currently using Node.JS with Express to handle routing. Views are generated with Jade/Pug View Engine. CORS requests are handled using the request framework on github. (https://github.com/request/request)
Currently, clients are stored within a MongoDB collection. When they enter their login information, their passwords are compared to a Hash stored in the DB. If everything checks out, a JWT is generated for the user and saved in the client DB and also stored within the browser in localStorage.token.
The user is then redirected to a splash page which has an authentication function as middleware. The authentication function accepts the token in three forms:
var token = req.body.token || req.query.token || req.headers['x-access-token'];
I feel like the way redirection is handled is a bit hacky. I am using window.location = route?token=[token]
Token is handed over to the authentication function within req.query.token, but this is exposed in URL. Here is my login function:
function submitLogIn() {
var credentials = {
email: logInForm.userEmail.value,
password: logInForm.pwField.value
}
fetch('/login', {
headers:{
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(credentials)
}).then(function(res){
if(!res.ok) alert("Error!")
else return res.json().then(function(result){
localStorage.token = result.token;
//This is how I'm doing redirecting:
window.location = '/selection?token=' + result.token;
}).catch(function(err){
console.log(err);
throw err;
});
});
Just for reference, this is the front-end route that it's going to at port 3001 which swings it over to 3000 as a reverse-proxy request:
router.post('/login', function(req, res, next) {
request.post({
url: 'http://localhost:3000/authorize',
form: req.body
}).pipe(res)
});
My main question is this: How can I handle redirection at the front-end within fetch calls?
My splash screen is basically a menu of buttons which will take the user to different pages. The pages will contain information that only admins should see. Let's say I want to click on this navigation button. It goes to a /GET route that requires the token to be sent for an OK status. if no token, it returns a 403.
router.get('/main', authorize.adminRequired, function(req, res, next) {
request.get({
url: 'http://localhost:3000/menus'
}, function(response, sensitiveItems) {
return res.render('index', {menu: JSON.parse(sensitiveItems)});
});
});
That authorize.adminRequired function needs a token in the form of a query, x-access-token, or req.body. Can't send a body in a GET request. Don't want to expose in URL, so:
Here's what I tried. I have this redirect fetch call as an onclick function.
function redirectToSensitivePage() {
fetch('/main', {
headers: {
'x-access-token': localStorage.token
},
method: 'GET'
});
};
This gives me a 200, OK. But I don't load the page. The res.render(index) in the route does not fire. The response does contain all of the data, but I am not redirected to the visual representation.
I could do the same hacky redirect using window.location=/main?token=[token], but I don't want to expose the URL token. I could load everything I need into the webpage upon login instead of having separate pages, have those divs hidden out of sight until keypress to make that div the main visible div, but I would rather figure out how to do it via redirect.
I'm still getting my bearings with using Node, so please let me know if I have made any stupid glaring oversights.
Edit: Should I simply try
var token = localStorage.token || req.body.token || req.query.token || req.headers['x-access-token'];
Is there any disadvantage to simply scraping from the localStorage automatically with each request?
I am working on express and I need to perform a POST request to an endpoint within the server. My code for this is :
request({
url : 'http://localhost:3000/api/oauth2/authorize',
qs:{
transaction_id:req.oauth2.transactionID,
user:req.user,
client : req.oauth2.client
},
headers:{
'Authorization':auth,
'Content-Type':'application/x-www-form-urlencoded'
},
method:'POST'
},function(err,res,bo){
console.log("Got response with body :"+bo);
});
localhost is the current server, this works properly but the session data is lost when i perform the POST request.
Is there any other way to perform a POST within the same server or to save the session data such that it is maintained after the POST?
Well, typically you register your routes something like:
var handlePostRequest = function(req,res,next) {
// process req.body etc.
};
app.post('/api/oauth2/authorize', handlePostRequest);
If you want to call that endpoint from within your application, you simply call handlePostRequest() providing the req, res, next objects as well.
Assuming handlePostRequest is in global scope, or required already; in your example that would be:
app.get('/some/other/endpoint', function(req,res,next){
// override the default req.body with your supplied data
req.body = {
transaction_id: req.oauth2.transactionID,
user: req.user,
client: req.oauth2.client
};
// you may also override req.headers etc. for authorization
// ...
// then call the "api" again with the new values
return handlePostRequest(req,res,next);
});
IF you however strictly want to make a POST request (for some reason), you need to supply the sessionID as well, which will be in your cookie. Then the session data will be available.