I have been struggling with the same issue for a while now, i'm trying to upload a file to my MS Teams OneDrive through the Graph-API but i dont have the authorization for it.
Reading the documentation to get my Token from Microsoft has so far done nothing for me as i am new to Javascript and React, so im having extreme difficulty getting it to work right. Can anyone give me an example of what the code looks like to get the authorization token that i need to access the Graph-API?
I have registered my Microsoft app and made a client-secret that i need in order to fetch the token.
Thank you in advance!
My code:
import React from 'react';
import './App.css';
import * as microsoftTeams from "#microsoft/teams-js";
class Tab extends React.Component {
constructor(props){
super(props)
this.state = {
context: {}
}
}
componentDidMount() {
var myHeaders = new Headers();
myHeaders.append("Content", "text/plain");
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Authorization", "Bearer {token}");
var raw = "This works";
var requestOptions = {
method: 'PUT',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
fetch("https://graph.microsoft.com/v1.0/sites/OpenSesameTest/Shared%20Documents/General/FileB.txt:/", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
}
componentDidMount() {
var myHeaders = new Headers();
myHeaders.append("Content", "text/plain");
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Authorization", "Bearer {token}");
var raw = "Fetch my token";
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
fetch("https://login.microsoftonline.com/openimstest/oauth2/v2.0/c7094fc6-9d30-429d-bb66-dd389295b426", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
}
render() {
return (
<form>
<div>
<label>Select file to upload</label>
<input type="file"></input>
</div>
<button type="submit">Upload</button>
</form>
);
}
}
export default Tab;
P.S.
I know im not actually using teh file input on my page but i want to do it as simple as possible at first, i'll be happy just to succesfully upload a file through the Graph-API at the moment.
Once again thank you!
EDIT:
The the fetch im trying to use in order to get the token:
componentDidMount() {
var myHeaders = new Headers();
myHeaders.append("Content", "text/plain");
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Authorization", "Bearer <token>");
var raw = "This works";
var requestOptions = {
method: 'GET',
grant_type: 'Unsure where to find my client_credentials',
client_id: 'my client-id',
scope: 'https://graph.microsoft.com/.default',
client_secret: 'my client-secret',
headers: myHeaders,
body: raw,
redirect: 'follow'
}
fetch("https://login.microsoftonline.com/openimstest/oauth2/v2.0/token", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
}
Im unsure where to find my client_credentials or what to put there. Also there is probably something else wrong with the fetch im trying to use.
Microsoft has made a library specially for getting tokens in single pages apps. It’s called #azure/MSAL-browser They even made a package specially for react. msal-react this page shows a getting started on how to use this in react. This package will handle the token request for you.
No client credentials flow
You can do this using client secret by following the steps below. Before you continue, Note that you should not be be using the client credentials flow on the SPA Reactjs application because there is no way to secure the client secret.
The other answer should had left it with this statement. Let me explain that. The client credentials flow is for application that run server side without user interaction. Anyone using this flow in a client app is using it incorrectly! So I’m my opinion we should not educate people in how it might be possible to misuse an authentication flow.
Have a look at this Single sign-on (SSO) support for tabs.
Follow document for creating app registration and setting up expose an API part.
Add route for all these file in your app. You might use the same route as mention in the app I shared below for folder structure.
<Route exact path="/signin" component={SignInPage} />
<Route exact path="/signin-simple-start" component={SignInSimpleStart} />
<Route exact path="/signin-simple-end" component={SignInSimpleEnd} />
Next have a look at this folder structure-> signin. Here you will see three files
sign-in.tsx -> This file route, you need to give in the manifest. It has the button that starts the authentication flow. Also in this file only, you need to give the route of the page you want to show when authentication is successful.
successCallback: () => {
history.push("/yourPage");
}
sign-in-start.tsx -> Here you need to call the authentication endpoint. So in the app that I shared we have backend with C# and from there we get the URL endpoint on the line 22 and push it in history. What you can do is you can directly create that URL(see below) and assign it to variable result(replacing the code from line 22 to 24 with this).
var result = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize?client_id=<client_id>&response_type=id_token token&redirect_uri=https://<app-domain>/signin-simple-end&response_mode=fragment&scope=<required-scope>&state=12345&nonce=abcde&login_hint=<user-principal-name>;
document.location.href = result;
sign-in-end.tsx -> In this file we get all the token that we have asked in the last step. So, in order to get access token you need to add this code
if (hashParams["access_token"]) {
localStorage.setItem("auth.result", JSON.stringify({
accessToken: hashParams["access_token"]
}));
}
in useEffect where we are getting other tokens. The above code checks if we get the access_token and set it in localStorage later on you can get it with localStorage.getItem("auth.result").
You might need to do little manipulation on localStorage.getItem("auth.result") in order to get the token.
Above method get us the delegated Graph API permission, so you need to give the delegated permission in your app according to your graph call.
You can do this using client secret by following the steps below.
Before you continue, Note that you should not be be using the client credentials flow on the SPA Reactjs application because there is no way to secure the client secret.
Note that If you have to use client secret there then you should remove the interaction with Graph to the serve side and then secure your app using some other way see - this Tutorial: Call the Microsoft Graph API in a Node.js console app for Nodejs Secured Daemon Service authentication
For getting access token using Client Secret.
Add the required application permission on Azure AD for uploading the file to onedrive. In this case Files.ReadWrite.Al. This permission will require admin approval.
Acquire an access token from Azure AD using the request below in fetch format.
curl --location --request GET 'https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token'
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=client_credentials'
--data-urlencode 'client_id=client-id'
--data-urlencode 'scope=https://graph.microsoft.com/.default'
--data-urlencode 'client_secret=client-secret'
As I said, client secret will not be secure when used in the Single Page Reactjs application.
The better option is to use auth code flow which will be more secure in your SPA case. Follow this for reactjs Tutorial: Sign in users and call the Microsoft Graph API from a React single-page app (SPA) using auth code flow
This will just require:
Adding Files.ReadWrite as delegated permission on AAD
Signing in with a user that has access to the teams team and authenticating as them.
Call PUT /groups/{group-id}/drive/items/{item-id}/content where the group id is the team id.
Related
I have created a web scene on my arcgis online portal and hosted it there also. Now I want to load the webscene on map through arcgis javascript api v4.1.6 and I want to pass the credential(like a token which I can get from argis js api with the right client id and client secret) through code.
Here is my code for loading the web scene
let scene = new WebScene({
portalItem: { // autocasts as new PortalItem()
id: "0614ea1f9dd043e9ba157b9c20d3c538" // ID of the WebScene on the on-premise portal
}
});`
let myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
var formdata = new FormData();
formdata.append("client_id", "");
formdata.append("client_secret", "");
formdata.append("grant_type", "client_credentials");
formdata.append("expiration", "20160");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
let token = await fetch("https://www.arcgis.com/sharing/rest/oauth2/token", requestOptions)
When I want to check the map in my website, it always prompt a popup window and ask for user name and password. So I am curios is it possible to feed the token somewhere in the code when I load the web scene? So it won't ask username and password from user.
Can you please provide me some sample code in ArcGIS API JavaScript v4.1.6?
Thanks!
It is possible to bypass the login prompt with the Esri Resource Proxy. However, the README does say that "it is not permitted to embed credentials in a resource proxy for the purpose of bypassing Named User authentication (i.e. the principle that each end-user must have their own unique login)".
Here is another possible workflow:
Pass the token generated at https://www.arcgis.com/sharing/rest/oauth2/token in a registerToken() to access the non-public items. With that, every AJAX request made by the application forwards this token when accessing web maps and other items stored in ArcGIS Online, or resources on your server.
var url = "https://www.arcgis.com/sharing/rest/oauth2/token";
var token = "";
esriRequest(url, {
query: {
client_id: "<CLIENT_ID>",
client_secret: "<CLIENT SECRET>",
grant_type: "client_credentials"
},
method: "post"
})
.then((response) => {
token = response.data.access_token;
esriId.registerToken({
server: "https://www.arcgis.com/sharing/rest",
token: token
})
})
.catch((err) => {
if (err.name === 'AbortError') {
console.log('Request aborted');
} else {
console.error('Error encountered', err);
}
});
A few things to note about this workflow:
The non-public items have to be owned by the same user who generated the client id and secret.
The layers on the web map / scene have to be either public or owned by the user who generated the client id and secret. That said, if you do need to include a non-public layer created by another user, you can create an item referencing that layer using the following workflow, and then add this new item to the web map / scene -
Add items from the web: https://doc.arcgis.com/en/arcgis-online/manage-data/add-items.htm#ESRI_SECTION1_1A21D51E1AFC41EA9974209BD94E50C0
Related documentation:
IdentityManager.registerToken: https://developers.arcgis.com/javascript/latest/api-reference/esri-identity-IdentityManager.html#registerToken
request.esriRequest: https://developers.arcgis.com/javascript/latest/api-reference/esri-request.html#esriRequest
If you want do it public, that is what I am understanding, you can set proxy that handle the security of the services. ESRI have open resources for similar tasks, take a look at this,
ESRI Git - Resources - Proxy
I'm trying to mock up some persisted log-in for my first web application so the site is still functional after a refresh. When I print the token (which is saved in cookies) in the console, it prints normally. And when I use postman with the token in the header, I get the correct JSON response. However, when using it in the mounted method, I get a 401. So I believe it is an issue with the way I'm am implementing my headers in my fetch. Thanks in advance, as I am extremely new to coding.
mounted: function() {
console.log(this.$cookies.get('token'));
let t = JSON.parse(JSON.stringify(this.$cookies.get('token')));
let h = new Headers();
h.append('Authentication', `Bearer ${t}`);
fetch('http://localhost:8080/api/owner/persist', {
method: 'GET',
headers: h
})
.then((response) => {
return response.json();
})
.then((data) => {
this.jwtUser = data;
})
Java Controller below: if I have the PreAuthorize Tag, I get a 401 error, and if I take it away I get a null pointer exception. I think its just something wrong with the formatting of my header. Which I have been messing around with a lot.
#PreAuthorize("isAuthenticated()")
#RequestMapping(path = "api/owner/persist", method = RequestMethod.GET)
public Owner persistedLogin(Principal principal) {
Owner o = new Owner();
o = ownerDAO.getOwnerInfoByName(principal.getName());
return o;
}
The standard way to transport access tokens, and especially JWTs, is the header called Authorization.
In your code example you are using Authentication which is from a description point of view correct as JWTs are in the first step authenticating a request and only at the second step source for authorization. But the standard header is like it is and was named Authorization. Your formatting of the header-value (Bearer <token>) looks correct to me.
Double check the correct name of your header that needs to carry the token, and verify you are using the correct one which is working as you stated in your test with Postman.
Best,
cobz
I am trying to fetch git azure devops api to get information about repositories and branches in js.
In order to achieve that, I made a little application with the following code :
$(document).ready(function() {
var personalToken = btoa(':'+'<personnalAccessToken>');
fetch('https://dev.azure.com/<company>/<project>/_apis/git/repositories?api-version=5.1', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
'Authorization': 'Basic '+ personalToken
}
}).then(function(response) {
return response.json();
}).then(function(repositories) {
console.log("There are "+repositories.count+" repositories");
}).catch(function(error) {
console.log('Fetch error: ' + error.message);
});
This code is working great but as you can see there is my personnalAccessToken writen directly inside the code... which is really bad...
When I am using git in command line, I don't have to specify any credential information because I use git credential manager for windows. Which means my personnalAccessToken is already stored, cached and automatically used everytime I use a git command, like clone, etc.
So, I would like my js code to use the same thing, I would like it to use my stored credentials automatically to fetch the api without being required to set my personnalAccessToken in code.
I have already searched for hours but can't find out if it is possible.
I have already searched for hours but can't find out if it is
possible.
Sorry but as I know it's impossible. The way you're calling the Rest API is similar to use Invoke-RestMethod to call rest api in Powershell.
In both these two scenarios, the process will try to fetch PAT for authentication in current session/context and it won't even try to search the cache in Git Credential Manager.
You should distinguish the difference between accessing Azure Devops service via Rest API and by Code:
Rest API:
POST https://dev.azure.com/{organization}/{project}/{team}/_apis/wit/wiql?api-version=5.1
Request Body:
{
"query": "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] = 'Task' AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc"
}
Corresponding Code in C#:
VssConnection connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), new VssClientCredentials());
//create http client and query for resutls
WorkItemTrackingHttpClient witClient = connection.GetClient<WorkItemTrackingHttpClient>();
Wiql query = new Wiql() { Query = "SELECT [Id], [Title], [State] FROM workitems WHERE [Work Item Type] = 'Bug' AND [Assigned To] = #Me" };
WorkItemQueryResult queryResults = witClient.QueryByWiqlAsync(query).Result;
Maybe you can consider using a limited PAT, limit its scope to Code only:
I know there exists other Authentication mechanism
:
For Interactive JavaScript project: ADALJS and Microsoft-supported Client Libraries.
You can give it a try but I'm not sure if it works for you since you're not using real Code way to access the Azure Devops Service... Hope it makes some help :)
If you have the script set up in an Azure Runbook you can set it as an encrypted variable there and have it pull it from there before running rather than having it directly written into the code.
$encryptedPatVarName = "ADO_PAT"
$adoPat = Get-AutomationVariable -Name $encryptedPatVarName
$adoPatToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($adoPat)"))
$adoHeader = #{authorization = "Basic $adoPatToken"}
The above is the Powershell version of it. I have seen some people do it with other
What combination of requests and responses are needed to get an Oauth token from eBay? What is a runame and what headers do I need to keep eBay happy?
After three frustrating days of trying to get Ebay's oauth to give me an access token, I have finally worked it out. As the docs are pain and there is little to no help online, I have decided to post my solution here in the hope that it will help others. I am no good at StackOverflow so let me know if I need to improve my formatting.
app.get("/login/ebay", (req, res) => {
res.redirect(`https://auth.sandbox.ebay.com/oauth2/authorize?client_id=DeanSchm-TestApp-SBX-b843acc90-fd663cbb&redirect_uri=Dean_Schmid-DeanSchm-TestAp-kqmgc&response_type=code`
);
});
The first thing you need to do is redirect to this URL.
The format is like this
https://auth.sandbox.ebay.com/oauth2/authorize?client_id=&redirect_uri=&response_type=code
There is also a scope property, but I don't understand that yet, and I got back a token without is so me.
That URL takes you to the eBay login page. If you are using the sandbox, you need to create a sandbox user and login with sandbox credentials.
Once you log in, eBay will redirect you to a URL of your choosing. You enter the URL you want to be redirected to here.
It's in the ebay developer section under Get A Token From Ebay Via your Application.
This URL can be anything. you just have to handle it in node or express or whatever, because as soon as someone signs in that URL is where they are heading.
Here is how I handled it
app.get("/auth/ebay/callback", (req, res) => {
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization:
"Basic " +
btoa(
`client public key:client secret keys`
)
},
data: qs.stringify({
grant_type: "authorization_code",
// parsed from redirect URI after returning from eBay,
code: req.query.code,
// this is set in your dev account, also called RuName
redirect_uri: "Dean_Schmid-DeanSchm-TestAp-kqmgc"
})
})
.then(response => console.log(response))
.catch(err => console.log(err));
});
A few gotchas that got me.
Make sure you have space after "Basic " in the authorisation
header.
bota is a 3rd party library that base 64 encodes your public and
secret keys. There are many ways to do this. I just did it this way because I stole a bunch of code.
With Axios, the request body is called data but with fetch and other
methods it might be called something else like body or param
The Axios method is in a get request because of the redirect from ebay
defaults to an http get.
ebay now uses https. Make sure you are using
sandbox URLs
We also had to use JS for the eBay API and solved your mention problem with developing a new Lib. It's available here. This lib will also automatically try to refresh the token if it's expires.
This is how we obtain the oAuth token:
import eBayApi from 'ebay-api';
const eBay = new eBayApi({
appId: '-- or Client ID --',
certId: '-- or Client Secret',
sandbox: false,
siteId: eBayApi.SiteId.EBAY_US,
ruName: '-- eBay Redirect URL name --' //in this case: Dean_Schmid-DeanSchm-TestAp-kqmgc
});
// This will generate the URL you need to visit
const url = eBay.oAuth2.generateAuthUrl();
// After grant access, eBay will redirect you to RuName page and set the ?code query.
// Grab the ?code and get the token with:
eBay.oAuth2.getToken(code).then((token) => {
console.log('Token', token);
ebay.oAuth2.setCredentials(token);
// Now you can make request to eBay API:
eBay.buy.browse.getItem('v1|382282567190|651094235351')
.then(item => {
console.log(JSON.stringify(item, null, 2));
})
.catch(e => {
console.log(e);
});
});
Another example with scope can we found here.
Some hints:
with "scope" you tell eBay what you plan to use. You can find the
Descriptions here, under Sandbox/Production Keys Box. (OAuth
Scopes)
if you use axios you can use the auth config, so you dont't
need btoa:
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
// ...
auth: {
username: 'appId',
password: 'certId'
}
});
To use sandbox without https, e.g. localhost, you can setup a redirect on a https site and redirec/pass the code to non-https site.
I would like to create a YouTube playlist on a users account, but I have struggled to authenticate a POST to the YouTube v3 api.
I'll start by showing how far I have got with this problem.
YouTube API Documentation
The Youtube API Documentation provides details on creating a playlist, and has a working example in the API Explorer
I entered the following code into the request body:
{
"snippet":
{
"title":"Test Playlist"
}
}
This successfully created a playlist on my YouTube account with the same title. So from this I could tell that, a title was required within the body and it would require OAuth 2.0 authentication (an error is displayed if it is not enabled) using one the scopes: youtube, youtube.force-ssl, youtubepartner.
First attempt in react
The First thing I tried was similar to this:
fetch('/youtube/v3/playlists', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer' + api.youtube,
},
body: JSON.stringify({
"snippet":
{
"title":"Test"
}
})
}).then(response => response.json()).then(data => {
console.log(data)
})
api.youtube contains my YouTube api key.
Most of the formatting for this came from another API I have in the same program for getting data from spotify which works.
The response I got from this would say "Login failed" or "Authentication Error" (something along those lines)
Anyway, this is relevant because I know that my first hurdle is getting authentication.
Authentication
The YouTube API Documentation contains a guide titled Implementing OAuth 2.0 Authorization I followed the guide for client side web apps.
The first thing I noticed is that they are using a library, I found this on npm under googleapis and installed it.
When I tried to call this in React using
const {google} = require('googleapis');
I won't get deep into the error but react said "Can't convert undefined to object" and found an issue which said that googleapis is intended for server side not client side, I tried building the react app and putting it on herokuapp but got the same error. Someone else suggested using gapi-client on npm which is a node wrapper for googleapis.
The next thing I did was try the example on the npm page, which is very similar to the google example for configuring the client object. I have it so the import part and function are at the top of my app.js and then the gapi.load part activates after a button is pressed (this could be useless info but w/e)
import gapi from 'gapi-client';
//On load, called to load the auth2 library and API client library.
gapi.load('client:auth2', initClient);
function initClient() {
gapi.client.init({
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],
clientId: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/drive.metadata.readonly'
}).then(function () {
// do stuff with loaded APIs
console.log('it worked');
});
}
I copied my client ID in from the API Console and this is the exact response I got:
FireFox
Loading failed for the with source
“https://apis.google.com//scs/apps-static//js/k=oz.gapi.en.WcpMzqgmJZU.O/m=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCNsTS1p4dx0iMhlrwEpiaXw4iMjOg/cb=gapi.loaded_0”.
Chrome
GET
https://apis.google.com//scs/apps-static//js/k=oz.gapi.en.WcpMzqgmJZU.O/m=auth2,client/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCNsTS1p4dx0iMhlrwEpiaXw4iMjOg/cb=gapi.loaded_0
net::ERR_ABORTED 404
That's about as far as I got and I'm not sure what to do from here, so any help is much appreciated. I hope this didn't get too convoluted but I've tried to convey my problem as clearly as possible.
So I was able to authorize the YouTube API and create a playlist.
I have a backend hosted on localhost:8888 (doesn't matter just not what react is hosted on).
here is sample code for what I put in the server.js file (for the backend)
var express = require('express');
var app = express();
var passport = require('passport');
app.use(passport.initialize());
var YoutubeV3Strategy = require('passport-youtube-v3').Strategy;
passport.use(new YoutubeV3Strategy({
clientID: YOUR_CLIENT_ID,
clientSecret: YOUR_CLIENT_SECRET,
callbackURL: 'http://localhost:8888/redirect',
scope: ['https://www.googleapis.com/auth/youtube']
},
function (accessToken, refreshToken, profile, cb) {
var user = {
accessToken: accessToken,
refreshToken: refreshToken
};
return cb(null, user)
}
));
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
app.get('/authenticate', passport.authenticate('youtube'))
app.get('/redirect', passport.authenticate('youtube', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('http://localhost:3000' + '?access_token=' + req.user.accessToken)
})
app.listen(8888)
This is using Passport.js to do oauth for me, lots of documentation can be found on the site.
In react I have it so a button will open localhost:8888/authenticate and then that will redirect back to my application. If you are using this you need to make sure that on your google API credentials you have the javascript origin as http://localhost:8888 and the redirect URI as http://localhost:8888/redirect and the correct scope and application type.
This is the function I use in my app.js (react) to make the POST
getAPIdata() {
let parsed = queryString.parse(window.location.search);
let accessToken = parsed.access_token
fetch('https://www.googleapis.com/youtube/v3/playlists?part=snippet', {
method: 'POST',
headers: {
'Content-type': 'application/json',
'Authorization': 'Bearer ' + accessToken,
},
body: JSON.stringify({
'snippet':
{
'title':this.state.inputTitle
}
})
}).then(response => response.json()).then(data => {
console.log(data)
window.alert('https://www.youtube.com/playlist?list=' + data.id)
})
}
I was actually mostly correct with the first attempt I just had the authorization incorrect.
Here's a couple sources that helped me make my solution:
Passport.js oauth tutorial
Googles OAuth 2.0 Playground
Passport.js Documentation
Passport.js facebook oauth example
Hopefully this is helpful to someone, You can use the same code i used in server.js to authenticate most services by just changing the strategy.
A live version of my application can be found here. In the console it shows the response from the POST request, this should help if you have any issues. I know the alert is bad ui but this wasn't the intended use.
Thanks for reading :)