I have an simple web app I'm testing on localhost (using http-server) in which I'm trying to authorise it following the GitHub tutorial.
I was able to redirect to GitHub page so the user can login there and get the temporary code returned from GitHub as query parameter.
Yet I can't get auth token because every time I send a POST request with all the required data I'm getting CORB error.
The code I'm using to do that:
const getGitHubToken = async code => {
return await fetch(authData.accessTokenURL, {
method: 'POST',
body: {
client_id: authData.client_id,
client_secret: authData.client_secret,
code
},
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
};
So my questions are:
why isn't it working
is it safe to keep client_id and client_secret on client side
any suggestions if it's good idea to apply this approach when my aim is to create an app able to query GitHub API (general stats, public repos), how can I do it better?
Related
Couple things to keep in mind, I'm a fairly beginner computer science student with a couple big projects under my belt. I mostly have been working with java so web is kinda new but seems easy enough to get used to.
I'm trying to send a post request to an endpoint that will return a membership id that I need.
I've been trying to follow along with these two documentation/guides:
https://github.com/Bungie-net/api/wiki/OAuth-Documentation
https://lowlidev.com.au/destiny/authentication-2#/tab-popup
On a side note, I really feel like there aren't many "get started" guides out there for working with the Bungie API but maybe that's intentional to prevent people from doing stupid stuff with people's accounts.
Anyways, I have successfully done the first part which is authorizing my app (registered on the bungie developers site) with the user's account using my client ID provided by Bungie. This returns a code inside the url that is your authorization code with the user's account. I have a kind of janky way of pulling it from the url, so any ideas of how I can improve it would be most appreciated.
document.getElementById("authButton").addEventListener("click", function(event) {
location.href = "https://www.bungie.net/en/OAuth/Authorize?client_id={my-client-id}&response_type=code&state=" + makeState(32);
});
var authCode = undefined;
if (window.location.href.includes("code=")) {
removeAuthButton();
authCode = window.location.href;
console.log(authCode);
var codeLoc = authCode.indexOf("code=");
var codeEndLoc = authCode.indexOf("&", 15);
authCode = authCode.substring(codeLoc + 5, codeEndLoc);
console.log(authCode);
}
This code is just executed after pressing a simple authorization button in the webpage, and bungie handles the redirect back to my page for me when I register my app.
Now after this, the documentation shows a POST request I need to make with the following:
POST https://www.bungie.net/platform/app/oauth/token/ HTTP/1.1
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
To stay with the urlencoded format, I have tried this code using an answer I found:
https://stackoverflow.com/a/53189376/16910197
document.getElementById("linkButton").addEventListener("click", function(event) {
fetch('https://www.bungie.net/Platform/App/OAuth/token/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'client_id': "{my-client-id}",
'grant_type': "authorization_code",
'code': authCode
})
}).then(function(response) {
console.log(response);
return response.json();
});
});
This isn't working and I have modified it every way possible to try and get it to work to no avail. What am I missing?
Here is what I am getting back if needed:
Response { type: "cors", url: "https://www.bungie.net/Platform/App/OAuth/token/", redirected: false, status: 400, ok: false, statusText: "Bad Request", headers: Headers, body: ReadableStream, bodyUsed: false }
I'm a little new to stackoverflow and this is my first question, so let me know if I can format it any better.
You need to call toString on URLSearchParams and send the Authorization header. If you haven't already, you should switch your API key to use the "Confidential" OAuth Client Type
document.getElementById("linkButton").addEventListener("click", function(event) {
fetch('https://www.bungie.net/Platform/App/OAuth/token/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${window.btoa(`${bungie_client_id}:${bungie_client_secret}`)}`
},
body: new URLSearchParams({
'client_id': "{my-client-id}",
'grant_type': "authorization_code",
'code': authCode
}).toString()
}).then(function(response) {
console.log(response);
return response.json();
});
});
In case you didn't, check the network request in browser dev tools to see what JavaScript is actually POSTing to the endpoint.
From a fellow Destiny API dev, please capitalise "token" in the endpoint path before I lose my mind!
I'm trying to change my banner on Twitter using 'node-fetch' library, but I can't get past Authentification 1.0a which is needed to post something on Twitter. My last try was using headers.Authorization = "OAuth ACCESS_TOKEN ACCESS_SECRET" but it was a failure. So my question is, what is the correct way of using Auth1.0a in 'node-fetch'?
Thank you in advance!
Btw. the ACCESS_TOKEN and ACCESS_SECRET in the code are not mine, but randomly typed in.
fetch(`https://api.twitter.com/1.1/account/update_profile_banner.json`, {
method: 'POST',
body: {
banner: b64,
},
headers: {
Authorization: "OAuth 2123123415-kbZfcGdHqKxTLlazrgQtzhzhKgHhjgtrLZq6789gui th67jz27z7gh3xhr5ghhgjj1gjHNMthtzuthfnOp3hJwhS5frx"
}
}).then(results => results.json()).then(data => console.log(data))
This is a sample Authorization header from their docs:
Authorization: OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318622958", oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", oauth_version="1.0"
As you can see there are many more fields to consider, I suggest you read the docs carefully.
The placement of your Auth header is correct.
I have a MERN + Passport.js application that is using fetch to make a call to my express API in order to retrieve data stored in a cookie. I have my React frontend routed to localhost:3000, and my backend routed to localhost:3001. My cookies are not being saved, and thus my session is not persisting within my browser. It is not an issue with the express-sessions or passport middleware, as when I use POSTMAN to execute the necessary calls to my API, the cookie is stored and the session persists.
It is only when I attempt to pass this information through/to my front end that things go wrong. I have been stumped for a while and can't seem to find an answer anywhere.
This is the line that I am using to save the cookie:
handleLogin(event) {
event.preventDefault();
fetch("http://localhost:3001/users/login", {
// credentials: 'include',
credentials: 'same-origin',
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
})
// .then( (response) => response.json())
.then( (response )=> {
if(response.message){
alert(response.message);
}
})
Which correctly calls my API, which logs the current session, user data, and cookie.
Upon refreshing and making another request, I lose the cookie (it was never properly stored in the first place I think), and all session data.
This is the get request that I make whenever I navigate to a new page:
componentDidMount(){
var current_user = "";
fetch("http://localhost:3001/users/", {
// credentials: 'include',
credentials: 'same-origin',
method: "get",
headers: {
'Accept':'application/json',
'Content-Type': 'application/json'
}
})
// .then( (response)=> response.json())
.then( (response)=> {
if(response.user){
current_user = response.user;
this.setState({
user: current_user
}), ()=> console.log(response);
}
})
}
In response, I get an undefined user and no other response, and the cookie is never stored in my browser. But again, if I do this in POSTMAN, strictly doing the POST request followed by that GET request, the proper user data is returned and the cookie is shown in POSTMAN as well.
Any idea as to why fetch is not passing the cookie information back to my front end? I have tried both credentials: 'include' and credentials: same-origin.
Thank you!
It seems like the problem, or at least part of it, is your use of same-origin. From Mozilla docs (italics my own):
omit: Never send cookies.
same-origin: Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. This is the default value.
include: Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls.
The definition of "same origin" does not include different ports.
If you change same-origin to include, fetch should start managing your cookies correctly.
If not - do you know for sure that the cookie is "never stored in the browser"? With Chrome, you can check chrome://settings/siteData.
I have been using DialogFlow v1 before using simply jquery and it was pretty straigh forward working!
Now that I have to switch to V2 I am stuck on how to keep somehow same code but just modify with the V2!
I have been looking at this client library for V2:
https://github.com/dialogflow/dialogflow-nodejs-client-v2#using-the-client-library
But I dont wanna use Node.js I just dont want to do somthing like node server.js to run the app, also I am not sure if I can mix jQuery with Node.js.
My previous code v1 looked like this:
fetch(url, {
body: JSON.stringify(data),
// cache: 'no-cache',
// credentials: 'same-origin',
headers: {
'content-type': 'application/json',
"Authorization": "Bearer " + configs.accessToken,
},
method: 'POST',
mode: 'cors',
redirect: 'follow',
referrer: 'no-referrer',
})
.then(response => response.json()) // parses response to JSON
Well I swtiched to ES6 for making http request for dialogflow but I would want the same code to use for V2, is this possible? Also I can no longer see access token for v2, how are we suppose to handle the auth for http calls?
I am really confused with the new V2 and since we switched to Enterprise Edition Account it is a must for us to use v2 and it kinda sucks!
Edit:
I am checking this example from documentation:
POST https://dialogflow.googleapis.com/v2beta1/projects/project-name/agent/intents
Headers:
Authorization: Bearer $(gcloud auth print-access-token)
Content-Type: application/json
POST body:
{
'displayName': 'StartStopwatch',
'priority': 500000,
'mlEnabled': true,
'trainingPhrases': [
{
'type': 'EXAMPLE',
'parts': [
{
'text': 'start stopwatch'
}
]
}
],
'action': 'start',
'messages': [
{
'text': {
'text': [
'Stopwatch started'
]
}
}
],
}
But I am somehow confused on this part: Authorization: Bearer $(gcloud auth print-access-token) where do I get access-token?
I have already done this part: gcloud auth activate-service-account --key-file= which I have no idea what is it doing after activating! I was hoping I would get back some access-token from this, but there seem to be nothing just a message that says Activated Service...
To use Dialogflow V2 API with browser AJAX just like V1, there is no simple way, unless you have the access token. I've run into same issue and figured out it can't be done without using their client libraries (SDK) or "google-oauth-jwt". In my example i used nodejs - google-oauth-jwt package which provides "access token" for my application which was used for browser AJAX calls. You don't have to use their nodejs SDK library, in case, you're handling logic on client side.
Setup Instructions:
1.Configure V2 API from V1 on the dialogflow account, follow the migration guide. Download the JSON file which has a unique email and key values. You might want to grant access to your application by registering the domains.
2.Create a nodejs application and use "google-oauth-jwt" to get the access token. Also, make this as a service to call it before hand to have the access token ready before making any ajax calls. Here is sample code:
app.get("/your_sample_web_service_to_get_access_token", (req, res, next) => {
new Promise((resolve) => {
tokens.get({
//find this email value from the downloaded json
email: 'xxx#xxx.iam.gserviceaccount.com',
//find this key value from the downloaded json
key: '-----BEGIN PRIVATE KEY-----xxx',
//specify the scopes you wish to access: as mentioned in dialogflow documentation
scopes: ['https://www.googleapis.com/auth/cloud-platform']
},
(err, token) => {
//rest api response
res.json({
"access_token": token
});
resolve(token);
}
);
});
});
3.From your client JavaScript, make an AJAX call using the access token you get from above nodejs application. Here is the sample code:
app.service('chatbot', function ($http, $rootScope) {
this.callAPI = function (user_entered_query) {
//I used detectintent REST API endpoint: find the project name from your account.
var endpoint = "https://dialogflow.googleapis.com/v2/projects/xxx/agent/sessions/123456789:detectIntent";
var data = JSON.stringify({queryParams:{}, query_input:{text:{text:user_entered_query,language_code:"en-US"}},outputAudioConfig:{},inputAudio:""});
var headers = {
//use the token from nodejs service
"Authorization": "Bearer " +$rootScope.token
};
return $http.post(_url, _data, {"headers": headers});
}
});
I am using oauth2 (node.js and the connect-oauth library) to connect to the google contacts API version 3.0.
Doing so, I get a response such as:
{ access_token : "...",
"token_typen": "Bearer",
"expires_in" : 3600,
"id_token": "..." }
I am missing the refresh token used to get a new access token as soon as the latter is expired.
options for oauth2
{ host: 'accounts.google.com',
port: 443,
path: '/o/oauth2/token',
method: 'POST',
headers:
{ 'Content-Type': 'application/x-www-form-urlencoded',
Host: 'accounts.google.com',
'Content-Length': 247 } }
post-body
'redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&grant_type=authorization_code&client_id=CLIENTID&client_secret=CLIENTSECRET&type=web_server&code=4%2F3gbiESZTEOjiyFPLUhKfE_a_jr8Q'
NOTE: I tried to add approval_prompt=force from a similar question to the request-post_body but this resulted in an Error
{ statusCode: 400, data: '{\n "error" : "invalid_request"\n}' }
NOTE: I tried to add approval_prompt=force from a similar question to the request-post_body but this resulted in an Error
You don't need the approval_prompt param when you ask for a token. The *approval_prompt* param is for the authorization part.
I am missing the refresh token...
The only way you DON'T get a *refresh_token* is when:
use the Client-side Applications flow;
include the access_type=online param in the authorization code request.
So, try adding: access_type=offline, to the authorization code request.
Edit:
i.e.:
https://accounts.google.com/o/oauth2/auth?client_id=**your_client_id**&scope=https://www.googleapis.com/auth/plus.me&redirect_uri=http://localhost&response_type=code&access_type=offline
If you're getting 400 is because you are adding an invalid parameter or missing one.
Good luck!
One time I did this was testing - I had deleted the google authorisation token from the app - so it tried to get another one and it did but without a refresh token.
So check the app you are testing is not authorised for the account you are testing from (does that make sense?)