Google API - Oauth 2.0 Auth token flow - javascript

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.

Related

Single flow: sign user in via Google oAuth AND grant offline/server access?

I'm trying to implement Google sign-in and API access for a web app with a Node.js back end. Google's docs provide two options using a combo of platform.js client-side and google-auth-library server-side:
Google Sign-In with back-end auth, via which users can log into my app using their Google account. (auth2.signIn() on the client and verifyIdToken() on the server.)
Google Sign-in for server-side apps, via which I can authorize the server to connect to Google directly on behalf of my users. (auth2.grantOfflineAccess() on the client, which returns a code I can pass to getToken() on the server.)
I need both: I want to authenticate users via Google sign-in; and, I want to set up server auth so it can also work on behalf of the user.
I can't figure out how to do this with a single authentication flow. The closest I can get is to do the two in sequence: authenticate the user first with signIn(), and then (as needed), do a second pass via grantOfflineAccess(). This is problematic:
The user now has to go through two authentications back to back, which is awkward and makes it look like there's something broken with my app.
In order to avoid running afoul of popup blockers, I can't give them those two flows on top of each other; I have to do the first authentication, then supply a button to start the second authentication. This is super-awkward because now I have to explain why the first one wasn't enough.
Ideally there's some variant of signIn() that adds the offline access into the initial authentication flow and returns the code along with the usual tokens, but I'm not seeing anything. Help?
(Edit: Some advice I received elsewhere is to implement only flow #2, then use a secure cookie store some sort of user identifier that I check against the user account with each request. I can see that this would work functionally, but it basically means I'm rolling my own login system, which would seem to increase the chance I introduce bugs in a critical system.)
To add an API to an existing Google Sign-In integration the best option is to implement incremental authorization. For this, you need to use both google-auth-library and googleapis, so that users can have this workflow:
Authenticate with Google Sign-In.
Authorize your application to use their information to integrate it with a Google API. For instance, Google Calendar. 
For this, your client-side JavaScript for authentication might require some changes to request
offline access:
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
In the response, you will have a JSON object with an authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
After this, you can use the one-time code to exchange it for an access token and refresh token.
Here are some workflow details:
The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token after the user has been presented an authorization dialog requesting offline access. If you've specified the select-account prompt in the OfflineAccessOptions [...], you must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token
Therefore, you should use google-auth-library to complete this workflow in the back-end. For this,
you'll use the authentication code to get a refresh token. However, as this is an offline workflow,
you also need to verify the integrity of the provided code as the documentation explains:
If you use Google Sign-In with an app or site that communicates with a backend server, you might need to identify the currently signed-in user on the server. To do so securely, after a user successfully signs in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity of the ID token and use the user information contained in the token
The final function to get the refresh token that you should persist in your database might look like
this:
const { OAuth2Client } = require('google-auth-library');
/**
* Create a new OAuth2Client, and go through the OAuth2 content
* workflow. Return the refresh token.
*/
function getRefreshToken(code, scope) {
return new Promise((resolve, reject) => {
// Create an oAuth client to authorize the API call. Secrets should be
// downloaded from the Google Developers Console.
const oAuth2Client = new OAuth2Client(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope,
});
// Verify the integrity of the idToken through the authentication
// code and use the user information contained in the token
const { tokens } = await client.getToken(code);
const ticket = await client.verifyIdToken({
idToken: tokens.id_token!,
audience: keys.web.client_secret,
});
idInfo = ticket.getPayload();
return tokens.refresh_token;
})
}
At this point, we've refactored the authentication workflow to support Google APIs. However, you haven't asked the user to authorize it yet. Since you also need to grant offline access, you should request additional permissions through your client-side application. Keep in mind that you already need an active session.
const googleOauth = gapi.auth2.getAuthInstance();
const newScope = "https://www.googleapis.com/auth/calendar"
googleOauth = auth2.currentUser.get();
googleOauth.grantOfflineAccess({ scope: newScope }).then(
function(success){
console.log(JSON.stringify({ message: "success", value: success }));
},
function(fail){
alert(JSON.stringify({message: "fail", value: fail}));
});
You're done with the front-end changes and you're only missing one step. To create a Google API's client in the back-end with the googleapis library, you need to use the refresh token from the previous step.
For a complete workflow with a Node.js back-end, you might find my gist helpful.
While authentication (sign in), you need to add "offline" access type (by default online) , so you will get a refresh token which you can use to get access token later without further user consent/authentication. You don't need to grant offline later, but only during signing in by adding the offline access_type. I don't know about platform.js but used "passport" npm module . I have also used "googleapis" npm module/library, this is official by Google.
https://developers.google.com/identity/protocols/oauth2/web-server
https://github.com/googleapis/google-api-nodejs-client
Check this:
https://github.com/googleapis/google-api-nodejs-client#generating-an-authentication-url
EDIT: You have a server side & you need to work on behalf of the user. You also want to use Google for signing in. You just need #2 Google Sign-in for server-side apps , why are you considering both #1 & #2 options.
I can think of #2 as the proper way based on your requirements. If you just want to signin, use basic scope such as email & profile (openid connect) to identify the user. And if you want user delegated permission (such as you want to automatically create an event in users calendar), just add the offline access_type during sign in. You can use only signing in for registered users & offline_access for new users.
Above is a single authentication flow.

How to Get a valid access token for my API and Microsoft Graph from Azure Active Directory?

I am trying to set up a spa javascript app which logs the user into our azure active directory tenant and then retrieves profile information from microsoft graph and calls an azure function written in c# core (my API).
I have separate application registrations set up for my website and the api in azure active directory.
I'm using the MSAL.js library in the javascript spa website and I'm using the newer microsoft identity / v2.0 endpoints.
The SPA app signs into active directory as expected and is able to use the access token to make the call to graph for the profile information. In my azure function I validate the token and this fails with the error "IDX10511: Signature validation failed. Keys tried: ....."
Now if I remove Microsoft graph from the scopes when requesting a token I get a token that when passed to the azure function validates perfectly well but I can no longer retrieve profile data in the spa app?
How do I get both to work?
Its also worth noting that ive tested the tokens with jwt.io and it is also unable to verify the signature when graph is used.
Heres how I'm getting my token:
var msalConfig = {
auth: {
redirectUri: window.location.origin, // forces top level instead of specific login pages - fixes deep link issues.
clientId: "Client ID of the website app", //This is your client ID
authority:
"https://login.microsoftonline.com/my-tennant-guid" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
const msalUserAgent = new Msal.UserAgentApplication(msalConfig);
var requestObj = {
scopes: ["User.Read", "api://MyApi/Access"]
};
//when the spa starts up I login using redirects
msalUserAgent.loginRedirect(requestObj);
//then before calling an api I request a token using this method
acquireTokenSilent() {
var promise = msalUserAgent.acquireTokenSilent(requestObj);
return promise;
},
Try specifying the scopes as scopes: ["User.Read"] in the acquireTokenSilent() function.
Since an access token is only valid for one API.
If you need two, call acquireTokenSilent twice with different scopes.
It's okay to specify scopes for two APIs when signing in, but not when getting tokens. A token has an audience that specifies the target API. So you can't use a token for one API against another. And that's why it's only valid for one API.

How to use App Access Token to update Open Graph Tags

I want to dynamically update Open Graphs Tags on my website, but don't know how to do it properly.
I have understood you're not supposed to use the access token in a client side script, but how do I go about and update it otherwise.
I have seen examples online that does not use an access token, but I get an authorization error then.
Can I make any settings on facebook developer to skip access token or how can I make a call from my javascript, without showing the access token or app secret?
This is the part of my script which runs after modifying the meta:og tags:
//Update Open Graph
$.post(
'https://graph.facebook.com',
{
access_token: '123',
id: strCurrentUrl,
scrape: true
},
function(response){
console.log(response);
}
From Facebook:
for security, app access token should never be hard-coded into
client-side code, doing so would give everyone who loaded your webpage
or decompiled your app full access to your app secret, and therefore
the ability to modify your app. This implies that most of the time,
you will be using app access tokens only in server to server calls.

How to Call a Firebase Authenticated Cloud Endpoint from Javascript?

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.

Always using the same OAUTH code with Dropbox-js

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.)

Categories

Resources