I've written several Google Cloud Endpoints in Python and have followed the directions to require that calls to them come from users authenticated using Firebase. I need to call my Endpoints from a web app using JavaScript, but I can't seem to get the authentication working.
I'd like to use the Google APIs client (gapi) which comes with the added benefit of dynamically generating the client library from a provided discovery document. When I try using the gapi client, I can make the call to my API just fine, but I get an HTTP 401 as a response, along with the HTTP unauthorized message that my python source returns.
Google's documentation on the subject is rather sparse. I gather from one tutorial on the subject that a standard Ajax call can be used, but I don't see any documentation on how to call a Firebase authenticated endpoint from Gapi. My current concern is that the gapi client may not be set up (yet) to allow for the use of a discovery doc and also allow for the Authorization header to be set as Firebase Auth requires.
Is what I'm attempting even possible?
Any suggestions would be appreciated. Perhaps calling a Firebase Authenticated endpoint isn't possible using the gapi client.
Here's a rough outline of my gapi js code:
function(token) {
gapi.client.init({
apiKey: 'MY_API_KEY',
discoveryDocs: [MY_DISCOVERY_DOC_URL'],
clientId: 'MY_WEB_CLIENT_ID',
scope: 'profile'
}).then(function(){
return gapi.client.my.server.api.call();
}).then(function(response){
console.log(response.result.data)
}, function(reason){
console.log('Error: ' + reason.result.error.message)
});
}
I have been struggling with this for a while now and finally made it work. I found two options:
Option 1) If you want to use the gapi.client library:
There is a method called gapi.client.setToken(tokenObject) - documentation
However, it seems to be new (July '17) and little documentation or examples are available. I made it work doing the following (this is in angularJS with angular-fire but I hope you get what I am doing, basically ignore the "$scope")
// any time auth state changes, add the user data to scope
$scope.auth.$onAuthStateChanged(function (firebaseUser) {
$scope.firebaseUser = firebaseUser;
$scope.idToken = null;
// get the token from the firebase User Object
// Note that getToken() is deprecated and for me it did not work as desired
// use getIdToken() instead
firebaseUser.getIdToken().then(function (idToken) {
$scope.idToken = idToken;
});
});
// Now you can use setToken
// If from the docs you were thinking firebase's getIdToken() gives me TokenObject and gapi's setToken()
// expects a TokenObject so I'll just pass it - you'd be wrong! (at least for me - if it works for you please give me a heads up)
// You'll need to build your own token:
var homemadeToken = {
access_token: $scope.idToken.toString() // This feels so wrong
};
gapi.client.setToken(homemadeToken);
gapi.client.yourapi.getSomething().execute(function (resp) {
// Do stuff with the response
}
);
Option 2) Use jQuery's Ajax request - documentation
$.ajax(backendHostUrl + '/_ah/api/yourapi/v1/someendpoint', {
headers: {
'Authorization': 'Bearer ' + $scope.idToken // Here it worked without making a string first but I did not check why
},
method: 'GET',
success: function (resp) {
// Do stuff with the response
}
});
If after all of that your backend is still not accepting the tokens and you have migrated from endpoints v1 to v2, it might help migrating again as described here. Esp. make sure the lib folder is created again.
Even after SDK updates, I noticed that if and once you migrated from v1 to v2 the "lib" folder is never updated regardless of whether or not it hase been updated.
Still not working?
This github page fixes the issue on the BACKEND side for an earlier version - the backend did not accept firebase tokens and needed to be hacked. If you want to apply the changes as described there and you are using the latest "lib" folder's (writing in July '17) users_id_token.py as per migration guide, note that the file has changed and you need to go against the explicit commentary in that file's _verify_signed_jwt_with_certs method:
# Formerly we would parse the token body here.
# However, it's not safe to do that without first checking the signature.
and parse the token before checking the signature. From that file's comments it can be inferred however, that Google plans to put the entire logic elsewhere - hopefully firebase friendly and safely.
Related
I'm implementing social login on my website.
I was able to implement the "One tap" flow, but I need to have an alternative to handle the "cooldown" which prevents the popup from appearing, if the user blocked it or closed it.
So I followed the "Authorization" flow on Google documentation.
Until yesterday morning everything was working fine and I succesfully exchanged the code with a token by calling
https://oauth2.googleapis.com/token
or
https://accounts.google.com/o/oauth2/token
sending secret and everything.
In a first instance I used Postman, then I made a sample code in a Spring project, before preparing the final code in another Spring project.
The first run in the final project I started getting a 400 error, with the redirect_uri_mismatch error key.
And then I was never able to do the exchange anymore, I get the same error from Postman as well.
The config is correct (It never changed from when it was working).
How can I solve this??
Here's some code
FRONTEND
this.client = google.accounts.oauth2.initCodeClient({
client_id: this.clientId,
scope: "openid profile email",
ux_mode: "popup",
redirect_uri: this.redirectUri,
callback: (response) => {
debugger;
this.submitFakeForm({
clientId: this.clientId,
code: response.code
});
}
});
this.client.requestCode();
POSTMAN PARAMS
this.redirectUri is identical to the one passed here and set up on Google credentials
FOR THE MOST SKEPTICAL, THE AUTHORIZED REDIRECTS :)
They're repeated in couples, because one is for local development, one is for the integration environment.
And of course the production config is on another credential.
Nowhere in the docs is this, but I came across this answer here on stackoverflow and it's basically suggesting not to pass the real redirect_uri, but to use a fixed string postmessage.
I want to point up again that I was using the real redirect_uri yesterday and it worked.
I will do some tests again in the future and update here if something changes.
For now just know that using postmessage fixed the issue for me
also I will be using https://oauth2.googleapis.com/token as endpoint, since it's the one mentioned in the (awful) docs, although https://accounts.google.com/o/oauth2/token works just as well.
I am struggling with a tutorial from Hasura which I believe might never have worked. I think it might be missing a piece and I'm interested in the most appropriate way to bridge the gap between when I've created and what will work.
From that documentation, as well as the Auth0 VueJS documentation I have been able to create a working VueJS application that authenticates via my Auth0 project and gives me back a user profile complete with user photo and email. So that's all working.
Following the Hausura tutorial from this I added apollo. I am now able to access non-protected GraphQL queries from my API but not protected ones. The reason is pretty clearly the code I added from the tutorial here:
getAuth: () => {
// get the authentication token from local storage if it exists
// return the headers to the context so httpLink can read them
const token = localStorage.getItem('apollo-token')
if (token) {
return 'Bearer ' + token
} else {
return ''
}
},
A console.log shows that token is null even when I'm logged in. It's pretty obvious why too. localStorage was never updated to contain a token. So of course it's null. There's no place in the tutorial that suggests this should be set anywhere but clearly it should.
So I did some digging and in that file src/vue-apollo.js there is an onLogin and onLogout function which require an ApolloClient object and in the case of the login, also a token. This will set the local storage appropriately.
Currently, my login is done within src/view/Home.vue like so:
login() {
this.$auth.loginWithRedirect()
// ... maybe do something here ?
},
Now if I were to add something like this after the login, it will not actually be called because of the redirect. But I think I need to add something like this:
this.$auth.getTokenSilently(token => {
onLogin(this.$apolloProvider.defaultClient, token)
}
Perhaps I need to add this into like the mounted() or beforeCreate() live cycle hook in my src/App.vue? I'm really not sure how to proceed here. Does this tutorial even really work?
Furthermore, isn't storing an auth token in localStorage a bad idea because of cross site request forgery?
You are right about it not being detailed in the post. I've not run the project but following the link to the source code of the demo. The /callback route creates the key via Callback component at /app/src/router.js:25, it add an authentication guard as well.
Then on /app/src/components/Callback.vue:25 on created handleAuthentication from auth service is called.
created() {
this.$auth.handleAuthentication();
}
You can check the code in /app/src/auth/authService.js it uses the auth0 library instance to get the token and call localLogin that saves the token in line 92.
About the security, I would follow the advice in Token Storage from Auth0.
I am hosting my website with Microsoft Azure. I have set up a few Application Settings (which should act as Environment Variables) for a secret key and ClientID for some GET requests I am doing. I have spent the last 3 hours googling trying to find a solution. According to Azure's Docs, I need to use process.env.KEY, but that is not working. When that is done I get this error in the console of my website jQuery.Deferred exception: process is not defined ReferenceError: process is not defined
Everything that I have so found is Node.js, but I do not use that. My website is purely HTML, CSS and the occational JavaScript script. Anyone that has any answers for me that can either put me on the correct path or helps me solve the issue completely?
Edit: This is the code for my script.js
$(document).ready(function() {
// $(window).scroll(function(){
// if(this.scrollY > 20){
// $(".menu").addClass("sticky");
// }
// else {
// $(".menu").removeClass("sticky");
// }
// });
$('.menu-toggler').click(function() {
$(this).toggleClass("active");
$(".menu-menu").toggleClass("active");
});
// Check if streamer is live on twitch
const Url = 'https://api.twitch.tv/helix/streams?user_login=pokimane';
$.ajax({
url: Url,
type: "GET",
success: function(result) {
var json = JSON.stringify(result);
if (json.includes('"type":')) {
$(".twitch").addClass("live");
};
},
error: function(error) {
console.log(`Error ${error}`)
},
isLocal: true,
jsonp: true,
headers: {
'Client-ID': process.env.CLIENT_ID,
'Authorization': `Bearer ${process.env.CLIENT_AUTH}`,
'accept': 'application/vnd.twitchtv.v5+json',
}
});
});
NEWEST
This api need add bear token to request.
According to your description, your project is only html+js, which is originally a static resource, hard coding will definitely cause security issues. But you will be much safer using rest api now.
Because you first need to obtain the bear token, you need to refer to the official documentation for details.
Microsoft identity platform and OAuth 2.0 authorization code flow
If you want to make minimal changes to the project, you may need to use ROPC flow. You can refer my another in another post.
PRIVIOUS
You can use rest api to get application settings.
The JavaScript script in Html does not support node usage. The syntax of process.env.CLIENT_ID is suitable for use in complete nodejs projects. It is recommended to use restapi to get the value of the application settings you want.
My custom settings in portal.
You can access this site. Web Apps - List Application Settings.
Context --
I am building a web application that uses the Google Cal and Google+ API.
I will need to obtain a refresh token, since once a user authenticates with the site/app, some of the calls happen behind the scenes after they have logged in (and many of them happen after 1 hour, of which the initial access_token is valid for)
As I understand it, here is the flow I must follow:
Register a Web Application API through Google console - done.
Prompt the user to authenticate with my application, done through a call using the following config vars:
var config = {
'client_id': MY_CLIENT_ID',
'scope': 'https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/userinfo.email',
'response_type': 'code',
'access_type': 'offline'
};
Then, using the Google object returned through the auth() call above, make another call to get the access_token and refresh_token.
https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code=CODE_RETURNED
client_id=CLIENT_ID_RETURNED
client_secret=API_CLIENT_SECRET
redirect_uri=API_REDIRECT_API
grant_type=authorization_code
Yet, when I try to run this call I always get some type of error. Right now I am stuck getting the following:
{
error: "redirect_uri_mismatch"
}
I have the following listed as my redirect uri both on the Google API settings page, and in code:
http://localhost/
Any advice from someone that has worked with this flow before?
Do I need to set up something differently for obtaining a refresh token?
The issue as to why this whole process was failing was because I was not including the 'redirect_uri' in my initial call to get a code.
I should have had:
var config = {
'client_id': MY_CLIENT_ID',
'scope': 'https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/userinfo.email',
'response_type': 'code',
'access_type': 'offline',
'redirect_uri': MY_REDIRECT_URI
};
Then, that redirect_uri was hit with data, and I set up a simple node route to listen, generate, and then store the access and refresh tokens for each user that authenticated.
I'm using the official Dropbox JS library in a Node.js server. It only ever needs to authenticate as a single user, and it can't go through the whole OAUTH browser setup every time the server starts. I am attempting to write an auth driver that pretends to be like the NodeServer driver, but runs the callback straight away with a code that always stays the same.
Here's what I've got (it's coffeescript, but you get the idea):
myAuthDriver = {
authType: -> return "code"
url: -> return "http://localhost:8912/oauth_callback" # What the url would be if I were using NodeServer
doAuthorize: (authUrl_s, stateParam, client, callback) ->
authUrl = url.parse(authUrl_s, true)
callback({
code: "[a code I just got using the NodeServer driver]"
state: authUrl.query.state
})
}
Running authenticate with this driver set causes this error:
Dropbox OAuth error invalid_grant :: given "code" is not valid
The docs say that this should only occur with a broken auth driver (but it doesn't give any ideas for fixing it).
Does anyone with more knowledge of OAUTH or Dropbox know what's wrong here?
Note: I've found in several places online that Dropbox OAUTH codes never expire
Once you have an OAuth 2 access token, you can just do var client = new Dropbox.Client({token: '<your token>'});. No need for an auth driver at all.
(If you want an easy way to get an access token, consider using https://dbxoauth2.site44.com.)