Is it possible to limit the access given by a user so that it's read-only - youtube-api

I'm trying to use the YouTube Data API V2.0 to pull data insights for the videos/channels of our client. I have a developer key and a token that my client generates, and successfully figured out how to retrieve that information. My problem is, when my client uses the app for YouTube token generation, we are asking for an access that means EVERYTHING and to be able to "manage" their accounts.
This is a major concern for the client and they don't want us to have this kind of complete access. Is there a way to get a token generated with only read-only permission?
Thanks very much for any help!

I have successfully used https://googleapis.com/auth/youtube.readonly as a scope; if you ask for just that scope during the initial oAuth flow (and NOT for https://googleapis.com/auth/youtube at the same time, as that is the management scope which will override the readonly scope), then you will get a 403 error whenever attempting an action that requires management permissions (inserting, uploading, updating, deleting).
The google-api clients for v3 handle this quite smoothly, if you're using them. If you have written your own oAuth flow control, just make sure you have the sole readonly scope when requesting the initial token.
EDIT IN RESPONSE TO COMMENT: To view this in action (I'll use javascript to show), you can create a simple demo using the sample code provided by the API docs. Here's the general process:
1) In the Google API console, create a 'project' and authorize the YouTube API for that project (under the Services tab). Additionally,create a client ID for web applications (under the API access tab) and add in your domain as an authorized Javascript domain.
2) On your server, create and HTML file to serve as your interface (in this sample, it is designed to let you create a new playlist and add items to it). Here's the code, straight from the docs:
<!doctype html>
<html>
<head>
<title>Playlist Updates</title>
</head>
<body>
<div id="login-container" class="pre-auth">This application requires access to your YouTube account.
Please authorize to continue.
</div>
<div id="buttons">
<button id="playlist-button" disabled onclick="createPlaylist()">Create a new Private Playlist</button>
<br>
<label>Current Playlist Id: <input id="playlist-id" value='' type="text"/></label>
<br>
<label>Video Id: <input id="video-id" value='GZG9G5txtaE' type="text"/></label><button onclick="addVideoToPlaylist()">Add to current playlist</button>
</div>
<h3>Playlist: <span id="playlist-title"></span></h3>
<p id="playlist-description"></p>
<div id="playlist-container">
<span id="status">No Videos</span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="auth.js"></script>
<script src="playlist_updates.js"></script>
<script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
</body>
</html>
3) In that same location, create the script "playlist_updates.js" with this code (again, straight from the docs):
// Some variables to remember state.
var playlistId, channelId;
// Once the api loads call a function to get the channel information.
function handleAPILoaded() {
enableForm();
}
// Enable a form to create a playlist.
function enableForm() {
$('#playlist-button').attr('disabled', false);
}
// Create a private playlist.
function createPlaylist() {
var request = gapi.client.youtube.playlists.insert({
part: 'snippet,status',
resource: {
snippet: {
title: 'Test Playlist',
description: 'A private playlist created with the YouTube API'
},
status: {
privacyStatus: 'private'
}
}
});
request.execute(function(response) {
var result = response.result;
if (result) {
playlistId = result.id;
$('#playlist-id').val(playlistId);
$('#playlist-title').html(result.snippet.title);
$('#playlist-description').html(result.snippet.description);
} else {
$('#status').html('Could not create playlist');
}
});
}
// Add a video id from a form to a playlist.
function addVideoToPlaylist() {
addToPlaylist($('#video-id').val());
}
// Add a video to a playlist.
function addToPlaylist(id, startPos, endPos) {
var details = {
videoId: id,
kind: 'youtube#video'
}
if (startPos != undefined) {
details['startAt'] = startPos;
}
if (endPos != undefined) {
details['endAt'] = endPos;
}
var request = gapi.client.youtube.playlistItems.insert({
part: 'snippet',
resource: {
snippet: {
playlistId: playlistId,
resourceId: details
}
}
});
request.execute(function(response) {
$('#status').html('<pre>' + JSON.stringify(response.result) + '</pre>');
});
}
Finally, create the file "auth.js" -- this is the code that actually does the oAuth2 flow:
// The client id is obtained from the Google APIs Console at https://code.google.com/apis/console
// If you run access this code from a server other than http://localhost, you need to register
// your own client id.
var OAUTH2_CLIENT_ID = '__YOUR_CLIENT_ID__';
var OAUTH2_SCOPES = [
'https://www.googleapis.com/auth/youtube'
];
// This callback is invoked by the Google APIs JS client automatically when it is loaded.
googleApiClientReady = function() {
gapi.auth.init(function() {
window.setTimeout(checkAuth, 1);
});
}
// Attempt the immediate OAuth 2 client flow as soon as the page is loaded.
// If the currently logged in Google Account has previously authorized OAUTH2_CLIENT_ID, then
// it will succeed with no user intervention. Otherwise, it will fail and the user interface
// to prompt for authorization needs to be displayed.
function checkAuth() {
gapi.auth.authorize({
client_id: OAUTH2_CLIENT_ID,
scope: OAUTH2_SCOPES,
immediate: true
}, handleAuthResult);
}
// Handles the result of a gapi.auth.authorize() call.
function handleAuthResult(authResult) {
if (authResult) {
// Auth was successful; hide the things related to prompting for auth and show the things
// that should be visible after auth succeeds.
$('.pre-auth').hide();
loadAPIClientInterfaces();
} else {
// Make the #login-link clickable, and attempt a non-immediate OAuth 2 client flow.
// The current function will be called when that flow is complete.
$('#login-link').click(function() {
gapi.auth.authorize({
client_id: OAUTH2_CLIENT_ID,
scope: OAUTH2_SCOPES,
immediate: false
}, handleAuthResult);
});
}
}
// Loads the client interface for the YouTube Analytics and Data APIs.
// This is required before using the Google APIs JS client; more info is available at
// http://code.google.com/p/google-api-javascript-client/wiki/GettingStarted#Loading_the_Client
function loadAPIClientInterfaces() {
gapi.client.load('youtube', 'v3', function() {
handleAPILoaded();
});
}
Note in there the OAUTH2_SCOPES constant. It's set to allow full management access, so if you then visit the html page in your browser and click on the 'authorize' link, you should see the window asking you to grant your domain access to manage your YouTube account. Do this, and then the code becomes functional ... you can add playlists and playlist items to your heart's content.
If you, however, then modify auth.js so that the OAUTH2_SCOPES looks like this:
var OAUTH2_SCOPES = [
'https://www.googleapis.com/auth/youtube.readonly'
];
and clear your cookies (to avoid inheriting the permissions you already granted ... just closing the browser and relaunching ought to be enough), then try again (visit the HTML, click the authorize link), you'll see that this time it's asking you to grant permission only to VIEW the account rather than manage it. If you grant that permission, then when you try to add a playlist through the interface you'll get an error message appearing that says you can't create the playlist.
If you're not using javascript, but instead a server-side language, as I mentioned the gapi clients are quite smooth. However, the handling of oAuth2 scope in these clients is not quite as transparent, and they're by design 'greedy' (in that, as it abstracts a service endpoint to an object, it will request the most thorough scope it needs to do any of the actions at that endpoint ... so even if you only intend to do list calls, if the service has an update action as well the client will request full management privileges). This can be modified, though, if you want to get into the client code -- or you could use it as a model for creating your own simplified client that you can granularly control in terms of scope.
That's about as thorough as I can be without knowing your underlying technologies. Hope the explanation helps!

Related

How to get YouTube channel info from google oath2 authorisation [duplicate]

For the app I'm building I want the end user to login using gapi OAuth2 and
from there I want the app to look for a playlist on their YouTube channel
and load it.
The getAuthInstance method returns an object with a Google username. However
for my own particular username, a query to find channel id by username
returns no results. From some browsing online, this is apparently an issue
with certain YouTube accounts.
Is there any workaround for this issue?
If you have a valid OAuth 2.0 authentication/authorization (for example, obtained by using GAPI), then it's quite simple the determine the authenticated user's channel ID using the Channels.list API endpoint queried with the parameter mine=true:
mine (boolean)
This parameter can only be used in a properly authorized request. Set this parameter's value to true to instruct the API to only return channels owned by the authenticated user.
Upon invoking the endpoint, the property id of the returned Channels resource contains the channel ID of the authenticated user.
For what concerns a Javascript GAPI (i.e. Google’s Client Library for Browser-side JavaScript) implementation, the code would look like shown below (for a broader context look into this sample source file from Google: analytics_codelab.js):
var channelId;
function loadAPIClientInterfaces() {
gapi.client.load('youtube', 'v3', function() {
getUserChannel();
});
}
function getUserChannel() {
var request = gapi.client.youtube.channels.list({
part: 'id',
fields: 'items(id)',
mine: true
});
request.execute(function(response) {
if ('error' in response) {
displayMessage(response.error.message);
} else {
channelId = response.items[0].id;
}
});
}
Note that the code above (unlike that in analytics_codelab.js) uses the fields request parameter for to obtain from the Channels.list endpoint only the channel's ID info (it is always good to ask from the API only the info that is of actual use).

Set default domain when using "Sign In With Google"

Using the instructions for Google sign in located here, We are implementing the "Sign in with Google" button.
This implementation has been successful, however, we want to set a default domain other than gmail.com as this will be used by our Gsuite for education users. This would make it so that users can click the sign in button and just type their username, instead of username#gSuiteDomain.com
When this button is clicked:
The user is redirected to:
How do we make it so the second image defaults to our gSuite domain instead of gmail.com?
I am pretty sure is you set the "hd" parameter to the G Suite domain name that it will work.
Please let us know if that works.
You can't use the simple directions at the location I linked in the question, as it is only for a basic minimum authentication.
For customization, you need to access the full client API here (server side validation can stay the same.)
This allows you to pass information to the init functions:
<script src="https://apis.google.com/js/platform.js?onload=onLoadCallback" async defer></script>
Then:
window.onLoadCallback = function () {
gapi.load('auth2', initSigninV2);
function initSigninV2() {
gapi.auth2.init({
client_id: 'Your_client_id',
hosted_domain: "YourHostedDomain.com"
}).then(function (authInstance) {
if (authInstance != null && authInstance != undefined) {
gapi.signin2.render("IDOfSignInButton", { onsuccess: onSignIn });
}
});
}
The rest is the same as in the original tutorial

How to obtain a Google oauth2 refresh token?

The following code uses the Google oauth2 mechanism to sign in a user. We need to process updates to the user's calendar while the user is offline, so we ultimately need the 'refresh token'. Does the result from grantOfflineAccess() return the refresh token (below, I can see that response.code holds a value that might be the refresh token)?
How can I get a refresh token that can be used (server side) to create new access keys for offline access to a user's Google calendar?
<script type="text/javascript">
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
function initClient() {
gapi.client.init({
apiKey: 'MY_API_KEY',
clientId: 'MY_CLIENT_ID.apps.googleusercontent.com',
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'],
scope: 'https://www.googleapis.com/auth/calendar'
}).then(function () {
var GoogleAuth = gapi.auth2.getAuthInstance();
GoogleAuth.signIn();
GoogleAuth.grantOfflineAccess().then(function (response) {
var refresh_token = response.code;
});
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
There is a reason why you are having a problem getting a refresh token out of JavaScript. That reason being that it's not possible.
JavaScript is a client side programming language, for it to work you would have to have your client id and client secret embedded in the code along with the refresh token. This would be visible to anyone who did a view source on the web page.
I think you realize why that's probably a bad idea. The main issue is that gapi won't return it the library just doesn't have that ability (not that I have tried in raw JavaScript to see if the OAuth server would return it if I asked nicely).
You will need to switch to some server side language. I have heard that this can be done with Node.js, but haven't tried myself. And Java, PHP, Python are all valid options too.
Based from this post, you should include the specific scopes in your requests. Your client configuration should have $client->setAccessType("offline"); and $client->setApprovalPrompt("force");.
After allowing access, you will be returned an access code that you can exchange for an access token. The access token returned is the one you need to save in a database. Later on, if the user needs to use the calendar service, you simply use the access token you already saved.
Here's a sample code:
/*
* #$accessToken - json encoded array (access token saved to database)
*/
$client = new Google_Client();
$client->setAuthConfig("client_secret.json");
$client->addScope("https://www.googleapis.com/auth/calendar");
$_SESSION["access_token"] = json_decode($accessToken, true);
$client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($client);
//REST OF THE PROCESS HERE

Using a JSON user object for Authentication in AngularJS

So I've got an question about authentication and have been wondering how other people might handle this situation. I'm currently running an Angular app that is built on a Rails API.
So far for authentication I have a form that does a post to the Rails side which logs the user in and then sends them back to the Angular app on success. Once the cookie is set and the user is logged in, I'm able to access a user.json file which contains all the User information one might expect (Id, username, roles, rights, etc). Since verification all happens on Rails, if the user logs out then this information is removed. So the two states look like so...
Logged in
{
id: 99384,
name: "Username",
url: "//www.test.com/profiles/Username",
timezone: null,
rights: [ ],
roles: [
"admin"
],
}
Logged out
{
error: "You need to login or join before continuing."
}
So far I've seen all these millions of different ways to do auth for Angular, but it seems like nothing fits this type of method. So my question is, since the server is handling all of the verification, is there a way to just check if they user.json file is empty (displaying the error message) and if it is send the Angular app to the Rails login page? Is there really any point messing with Cookies, Tokens, etc when I can base it all on the JSON file?
You are already using cookies - the server is setting them. What you have done is a fairly standard way of doing things.
To check the json file, you can do something like this stub shows in your controller:
app.controller('AppControl', function($scope, $http, $location){
// Get the JSON file.
$http.get('/path/to/json/file')
.then(response){
if(response.data.error){
// redirect to login
$location.path('login');
}
else{
$scope.user = response.data;
// your app code here.
}
})
.catch(function (error){
// unable to reach the json file - handle this.
});
});
Of course, you should really move this out into a service so you can re-use it, and also cache the data, rather than getting the user every time you change route/page, but this gives you a vague idea.
EDIT Example factory:
.factory('User', function( $http ){
// Create a user object - this is ultimately what the factory will return.
// it's a singleton, so there will only ever by one instance of it.
var user = {};
// NOTE: I am assigning the "then" function of the login promise to
// "whenLoggedIn" - your controller code is then very easy to read.
user.whenLoggedIn = $http.get('user.json')
.then(function(response){
// Check to see if there is an error.
if (response.data.error !== undefined) {
// You could be more thorough with this check to determine the
// correct action (examine the error)
user.loggedIn = false;
}
else {
// the user is logged in
user.loggedIn = true;
user.details = response.data;
return user;
}
}).then; // <-- make sure you understand why that .then is there.
return user;
})
Usage in the controller
.controller('ExampleController', function($scope, User){
// It's handy to have the user on the scope - you can use it in your markup
// like I have with ng-show on index.html.
$scope.User = User;
// Do stuff only if the user is loggedin.
// See how neat this is because of the use of the .then function
User.whenLoggedIn( function (user){
console.log(user.details.name + " is logged in");
});
});
Because it's on the scope, we can do this in the html:
<body ng-controller="ExampleController">
<h1 ng-show="User.loggedIn == null">Logging in..</h1>
<h1 ng-show="User.loggedIn == true">Logged in as {{ User.details.name }}</h1>
<h1 ng-show="User.loggedIn == false">Not logged in</h1>
</body>
Here is an example on plunker where this is working.
Note the following:
If the user is/was already logged in, when you inject the service in the future, it won't check the file again. You could create other methods on the service that would re-check the file, and also log the user out, back in, etc. I will leave that up to you.
There are other ways to do this - this is just one possible option!
This might be obvious, but it's always worth saying. You need to primarily handle authentication and security on the server side. The client side is just user experience, and makes sure the user doesn't see confusing or conflicting screens.

Getting user profile with google oauth2

I'm trying to get user profile information upon logging in with google-oauth2. User successfully logs in and i can get the access_token and can refresh the token when needed.
Though i could not manage to get any information about the user despite reading the docs and trying for hours.
From "Retrieving profiles" section of developers guide :
https://www.google.com/m8/feeds/profiles/domain/domainName/full
should be enough. i've tried with "gmail.com", "google.com", "gmail", "google", "orkut", "orkut.com" , myregisteredappsdomainname (and .com) as domainName. i've also tried it with
https://www.google.com/m8/feeds/profiles/domain/domainName/full?access_token=access_token_for_user
all i managed to get was 401 error, where it says "That’s an error.". Regarding 401 error, I've refreshed the token and tried again with new token, but kept getting 401s.
How can i get profile information and image address for user upon logging in?
The scope you're looking for is:
https://www.googleapis.com/oauth2/v1/userinfo
This has been already answered here
I was getting similar errors requesting profiles even after correctly defining the scope and getting access tokens etc.. The trick for me was to include the API version on my requests. See here for more info http://code.google.com/googleapps/domain/profiles/developers_guide.html#Versioning
Maybe little late yet could this be helpful to someone. Below is the working code I wrote to get gplus user profile
In HTML below markup will display goolge signIn button
<span id="signinButton">
<span
class="g-signin"
data-callback="signinCallback"
data-clientid="YOUR GPLUS CLIENT ID"
data-cookiepolicy="single_host_origin"
data-scope="email">
</span>
</span>
Below is the java script
var access_token;
/**
* Called when the Google+ client library reports authorization status.
*/
function signinCallback(authResult) {
access_token = authResult.access_token;
gapi.client.load('plus', 'v1', function () {
gapi.client.plus.people.get({ userId: 'me' }).execute(printProfile);
});
}
/**
* Response callback for when the API client receives a response.
*
* #param resp The API response object with the user email and profile information.
*/
function printProfile(resp) {
if (resp.code != 403) {
console.log('name:' + access_token.givenname);
console.log('last name:' + access_token.lastname);
console.log('email:' + access_token.emails[0]);
console.log('gender:' + access_token.gender);
console.log('profile image url:' + access_token.image.url);
}
}
Please make sure that you load google api javascript asynchronously within the body tag as below
<script type="text/javascript">
(function () {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/platform.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
To handle logout refer to the answer I provide in below link, you will need to store access_token in backend so that during logout call this to be used, in my case I have stored in session and getting through ajax call
gapi.auth.signOut(); not working I'm lost
Hey why don't you look at the code given at:
http://www.codeproject.com/KB/aspnet/OAuth4Client.aspx
It definitely helps you. The project is actually an oauth playground to send correct oauth header to correct endpoints.

Resources