Do the client and tenant id need to be hidden in a Microsoft Graph SPA and associated GitHub repo? - microsoft-graph-api

I have a single page app that consists of these files:
index.html
authConfig.js (defines MSAL configuration and scopes)
graphConfig.js (defines graph endpoints used in the app)
ui.js (defines all the UI functionality)
authPopup.js (creates the main myMSALObj instance and signin and signout functions)
graph.js (contains the function that calls the MS Graph API endpoint using the authorization bearer token scheme)
My question is in regards to whether I need to 'hide' the following values in the authConfig.js file when adding the files to a GitHub repo (using environment variables etc):
// application/client id of app registration in Azure portal
clientId
// full directory URL, eg: https://login.microsoftonline.com/<tenant-id>
authority
// full redirect URL
redirectUri
My recollection is that it's not required because the values are easily discoverable via other means, but would like that confirmed.
The full contents of authConfig.js are below:
/**
* Configuration object to be passed to MSAL instance on creation.
* For a full list of MSAL.js configuration parameters, visit:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
*/
const msalConfig = {
auth: {
// application/client id of app registration in Azure portal
clientId: "*******",
// full directory URL, eg: https://login.microsoftonline.com/<tenant-id>
authority: "https://login.microsoftonline.com/*******",
// full redirect URL
redirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
/**
* Scopes you add here will be prompted for user consent during sign-in.
* By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
* For more information about OIDC scopes, visit:
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
const loginRequest = {
scopes: ["User.Read"]
};
/**
* Add here the scopes to request when obtaining an access token for MS Graph API, for more information, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
*/
const tokenRequest = {
scopes: ["User.Read", "GroupMember.Read.All", "Group.Read.All", "Directory.Read.All", "Group.ReadWrite.All", "Directory.ReadWrite.All", "Team.ReadBasic.All", "TeamSettings.Read.All", "TeamSettings.ReadWrite.All", "User.Read.All", "User.ReadWrite.All", "Sites.ReadWrite.All"],
forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token
};
For reference, index.html contains this:
<script src="https://alcdn.msauth.net/browser/2.26.0/js/msal-browser.js"
integrity="sha384-fitpJWrpyl840mvd9nBFLGulqR4BJzvim0fzrXQKdsVh2AQzE4rTTJ0o5o+x+dRK"
crossorigin="anonymous"></script>
<script type="text/javascript">
if (typeof Msal === 'undefined') document.write(unescape("%3Cscript src='https://alcdn.msftauth.net/browser/2.26.0/js/msal-browser.js' type='text/javascript' crossorigin='anonymous' %3E%3C/script%3E"));
</script>
<script type="text/javascript" src="js/authConfig.js"></script>
<script type="text/javascript" src="js/graphConfig.js"></script>
<script type="text/javascript" src="js/ui.js"></script>
<script type="text/javascript" src="js/authPopup.js"></script>
<script type="text/javascript" src="js/graph.js"></script>

Related

"Invalid OAuth access token" when using valid token

When trying to use the Deezer JS SDK to access /user/me, I keep getting error code 300 (Invalid OAuth access token). My code is mostly copied from the examples so I can't figure out why this would be happening. I've tried manually specifying the token in the API call, and directly accessing the API via HTTP and haven't gotten past the Invalid Access Token error. What am I doing wrong?
index.html:
<!doctype html>
<html>
<body>
<div id="dz-root"></div>
Login
Get Login Status
Me
<script type="text/javascript" src="http://cdn-files.deezer.com/js/min/dz.js"></script>
<script src="js/index.js"></script>
</body>
</html>
index.js:
DZ.init({
appId: "253122",
channelUrl: "http://mopho.local/deezer-channel.html",
});
document.getElementById("dtest").addEventListener("click", () => {
DZ.login(function(response) {
console.log("1",response);
}, {perms: 'basic_access,email'});
});
document.getElementById("lstat").addEventListener("click", () => {
DZ.getLoginStatus(function(response) {
console.log(response);
});
});
document.getElementById("getme").addEventListener("click", () => {
DZ.api('/user/me',
function(response) {
console.log("2",response);
}
);
});
deezer-channel.html:
<script src="http://cdn-files.deezer.com/js/min/dz.js"></script>
mopho.local is configured in my hosts file + nginx to point to 127.0.0.1.
My Deezer app has the following configuration:
Application domain: mopho.local
Redirect URL after authentication: http://mopho.local
This turned out to be a permissions issue. I changed the permissions to "basic_access,email,offline_access,manage_library,manage_community,delete_library,listening_history" and it worked. I'm not sure which of the returned data points were associated with which permissions, but my guess is that some of the permissions were changed on the back end and the examples in the docs haven't caught up.

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

How can I get the authorization token from localhost (ie without redirect_url)?

Let's say I want to obtain an authorization token from google via javascript/python/anything on localhost. How can I do that? After sending a authorization request on "https://accounts.google.com/o/oauth2/auth?..." user has to allow it, but there is no way my script obtained the token back (since google cannot redirect to localhost). Or is it?
I have been dealing with OAuth for the last couple of days, and I'm not sure I 100% understand it...but I will try to relay what I have learned.
Here is the code I used to ask a question earlier this week...
index.html
<!doctype html>
<html>
<head>
</head>
<body>
<p>Tripping all day...</p>
<p id="output"></p>
<script src="auth.js"></script>
<script type="text/javascript">
function init() {
console.log('init');
checkAuth();
}
</script>
<script src="https://apis.google.com/js/client.js?onload=init"> </script>
<script>
document.getElementById("output").innerHTML = "Coooooorrrrrraaaaalll";
</script>
</body>
</html>
auth.js
var CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com';
var SCOPES = 'email';
function handleAuth(authResult) {
console.log('handle auth');
console.log(authResult);
}
function checkAuth() {
console.log('check auth');
gapi.auth.authorize({client_id: CLIENT_ID, scope: SCOPES, immediate: false, cookie_policy: 'single_host_origin'}, handleAuth);
}
This uses the Gapi javascript client Google provides. When I call gapi.auth.authorize and give it my Client ID I set up in the Developer Console, it shows me the Google account authorization popup, and then I believe that the Gapi object has a method that adds the OAuth token to the Gapi object itself. I didn't include a redirect URI when I set up my credentials, by the way.
After I got the authorize call working, I could then call oauth2.userinfo.get() to get my token to use with their APIs.
var request = gapi.client.oauth2.userinfo.get();
As for the locahost, I used just the IP address of my development server which doesn't have a top level domain attached to it. Localhost may work the same way?

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

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!

Error "Origin null is not allowed by Access-Control-Allow-Origin" in jsOAuth

I'm trying to use the twitter API with library jsOAuth.
Full html
<div id="message">Loading..</div>
<script src="jsOAuth-1.3.3.min.js" type="text/javascript"></script>
<script src="http://code.jquery.com/jquery-1.7.min.js" type="text/javascript"></script>
<script type="text/javascript">
var oauth = OAuth({
consumerKey: "-MY-KEY-",
consumerSecret: "MY-SECRET"
});
Updated
oauth.get("http://api.twitter.com/1/statuses/home_timeline.json?callback=?", success, failure);
function success(data){
$("#message").html("Sucess: " + data.text);
var timeline = jQuery.parseJSON(data.text);
console.log(timeline);
$.each(timeline, function (element){
console.log(element.text);
});
}
function failure(data) {
console.log("Throw rotten fruit, something failed");
$("#message").html("Error" );
}
</script>
Result
Full image
Questions
What am I doing wrong?
How can I use the twitter API in my PC.
If you can send me I would appreciate any examples.
Thank you all.
What am I doing wrong?
You are trying to make an AJAX request to Twitter. You are violating cross domain access policies - you cannot access data on the Twitter domain.
How can I use the Twitter API on my PC?
Pick one :
Use JSONP
Use a server-side proxy
Store your code on a file:// path, which removes the cross domain restrictions
Change your browser's security settings to allow this kind of access.
I have styled the unlikely ones in italic.
Also, as an experienced Twitter developer, I have one important note to make about your code: you're using Javascript to access the API. While using JS to do that isn't disallowed, using OAuth in javascript is very unsafe and your application will be blocked from the Twitter API if you used this code on a website.

Resources