Office Add in not Authenticating to Webapi - javascript

Currently having issues accessing a web api controller that has the an autherize attribute and is registered to Azure AD. Currently I am getting a Token from ADAL.JS successfully and now am trying to make a ajax call to a test Webapi which will perform a back end service for my Office-Addin app. I have tried to scenarios which I will describe.
First Scenario:
I created to separate web application entries Azure management portal, one for my office Add in app so I can get a token, and the other one for my web api to I can lock it down. I then gave permission for my office-add application to my webapi in order for my office-Add in to talk with my web api. I get a 401 status unauthorized.
Second Senario:
Since I was getting unauthorized I proceeded to make a new application in my Azure Management portal and getting a token with the ADAL.js but when I make the same call to the webapi which is sharing the same client number as my office-Addin app I still get a 01 status unauthorized.
Not sure what I am doing wrong, seem like I have tried both possible ways but none are working for me. This is my java-script
window.config = {
tenant: variables.azureAD,
clientId: variables.clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
sharePointUri: "https://" + tenant + ".sharepoint.com",
ContentCenterApi: "https://localhost:44334"
},
cacheLocation: "localStorage"
};
var authContext = new AuthenticationContext(config);
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
function GetSharepointList(e) {
var authContext = new AuthenticationContext(config);
var user = authContext.getCachedUser();
if (!user) {
authContext.login();
}
else {
authContext.acquireToken(config.endpoints.sharePointUri, function (error, token) {
if (error || !token) {
console.log("ADAL error occurred: " + error);
return;
}
else {
var me = this;
//var SiteUrl = config.endpoints.sharePointUri + "/sites/Apps_Dev/ER_TestSite/";
$.ajax({
url: 'https://localhost:44334/api/Values/Get',
dataType: "json",
headers: {
"Authorization": "Bearer " + token,
"accept": "application/json;odata=verbose",
},
success: function (data) {
handleData(data);
}
}).done(function (response) {
console.log("Successfully fetched list from SharePoint.");
// var items = response.d.results;
//$("#contentPreview").append(items[0]);
}).fail(function (error) {
console.log("Fetching list from SharePoint failed.");
});
}
});
};
}

I'm a little confusing that the API you call is
"https://localhost:44334/api/Values/Get", but the resource id to acquire token is "config.endpoints.sharePointUri". It should be the App URI you registered on the Azure AD.
About how to secure the Web API by using Azure AD. The article below may give you some help.
Protect a Web API using Bearer tokens from Azure AD.
Single Page Application demo (adal.js): active-directory-angularjs-singlepageapp
This sample demonstrates the use of ADAL for JavaScript for securing an AngularJS based single page app, implemented with an ASP.NET Web API backend.

Related

How to access secure webscene through javascript api in esri js(without ask credential from user.)

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

Using Outlook REST API Beta From an Outlook add-in

I have created an outlook add-in with ReactJS and followed this guide to get a token to be able to use the Outlook v2.0 REST APIs: https://learn.microsoft.com/en-us/office/dev/add-ins/outlook/use-rest-api
Now I would like to start using the Outlook Beta REST APIs and I figured I could use the same token to make the API calls, however I get the following error which suggests I cannot use this token:
{"error":{"code":"UnableToReadToken","message":"OAuth token submitted with the request can not be parsed.","innerError":{"requestId":"b96fc800-82d4-4b6d-8aa0-0b9ff6a36873","date":"2020-02-21T09:27:27"}}}
Is there anyway to call this API by using the token generated by Office.context.mailbox.getCallbackTokenAsync? I am aware that I can probably get an oauth2 token via Azure AD, however within the Azure AD Portal I do not have the proper admin access to follow this process so I am looking for a solution which does not rely on that.
Here is a code snippet of my functions to get the token and call the API:
getToken() {
return new Promise(async function (resolve, reject) {
try {
Office.context.mailbox.getCallbackTokenAsync({ isRest: true }, function (result) {
if (result.status === "succeeded") {
let accessToken = result.value;
console.log(result.value);
resolve(accessToken);
} else {
console.log(result.status);
reject(result.status);
}
});
} catch (error) {
console.error(error);
reject(error);
}
})
}
getRules(token) {
return new Promise(async function (resolve, reject) {
try {
const url = 'https://outlook.office.com/api/beta/me/mailfolders/inbox/messagerules';
const header = new Headers({ 'Authorization': `Bearer ${token}` });
const options = {
headers: header
};
let response = await fetch(url, options);
let jsonResponse = await response.json();
console.log(jsonResponse);
resolve(jsonResponse);
} catch (error) {
console.error(error);
reject(error);
}
});
}
You mention not having the proper admin access to use the AD v2 authentication endpoint.
There are currently two approaches to handle app registration and user authorization. Did you confirm, if by chance one of these methods might still work...
Use Azure AD v2 authentication endpoint:
https://learn.microsoft.com/en-us/previous-versions/office/office-365-api/api/beta/use-outlook-rest-api-beta#RegAuthConverged
Use Azure Active Directory and OAuth:
https://learn.microsoft.com/en-us/previous-versions/office/office-365-api/api/beta/use-outlook-rest-api-beta#RegAuthAzure
...
Some additional information (which you might already be aware of):
The v2 authentication endpoint has been promoted from preview to Generally Available (GA) status for Outlook and Outlook.com developers.
If you have an in-production app that uses Windows Live API to access Outlook.com mailbox data, you must rewrite the app to use the the v2 authentication endpoint and the Outlook REST API. Because Windows Live API is being deprecated for Outlook.com, and Outlook.com users get their mailboxes enabled for the Outlook REST API, these users will get HTTP 404 errors when attempting to run such Windows Live API apps.
Read more here: https://learn.microsoft.com/en-us/previous-versions/office/office-365-api/api/beta/use-outlook-rest-api-beta

ADAL.JS to pick logged in user directly instead of showing account selection screen

Am expecting ADAL.js code to automatically pick the user and login into my web application instead of throwing user to account selection screen, where he just click on account and he gets in with out even entering password. but am looking to avoid account selection screen. it all should happen behind the scene.
function invokeGraphApi() {
// #4: Get an access token to the Microsoft Graph API
authContext.acquireToken(azurePorperties.endpoints.graphApiUri,
function (error, token) {
// TODO: Handle error obtaining access token
if (error || !token) {
displayAlert('danger', "User details not found, Kindly relogin");
return;
}
$.ajax({
type: "GET",
url: azurePorperties.endpoints.graphApiUri + "/v1.0/me",
headers: {
"Authorization": "Bearer " + token
},
success: function (result) {
console.log(result);
//disableWaitingPointer();
}, error: function (result) {
console.log("Error at GraphApi/V1.0/Me:" + result);
displayAlert('danger', 'Error occured at Graphapi/v1.0/me ');
}
});
}
);
}
The application has access to the user's authentication context and wants to avoid the Azure AD account selection prompt when multiple accounts are signed in. This can be done a few different ways:
Session Id or SID
You can pass the SID in the request parameters to the acquireTokenSilent call. This will allow Azure AD to bypass the account selection. SID , you can configure SID in optional claim
Login hint
If you do not have SID claim configured or need to bypass the account selection prompt in interactive authentication calls, you can do so by providing a login_hint in the request parameters
You can follow below doc for further reference:
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-js-sso#automatically-select-account-on-azure-ad
Also , you can find the detailed samples in below repo:
https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki#sso-between-apps
Hope it helps.

Keycloak JavaScript API to get current logged in user

We plan to use keycloak to secure a bunch of web apps, some written in Java, some in JavaScript (with React).
After the user is logged in by keycloak, each of those web apps needs to retrieve the user that is logged in and the realm/client roles that the user has.
For Java apps, we tried the keycloak Java API (request -> KeycloakSecurityContext -> getIdToken -> getPreferredUsername/getOtherClaims). They seem to work fine
For JavaScript apps, we tried the following code, but could not get Keycloak to init successfully (Note this is in web app code after the user is already authenticated by keycloak, the app is only trying to retrieve who logged in with what roles):
var kc = Keycloak({
url: 'https://135.112.123.194:8666/auth',
realm: 'oneRealm',
clientId: 'main'
});
//this does not work as it can't find the keycloak.json file under WEB-INF
//var kc = Keycloak('./keycloak.json');
kc.init().success(function () {
console.log("kc.idToken.preferred_username: " + kc.idToken.preferred_username);
alert(JSON.stringify(kc.tokenParsed));
var authenticatedUser = kc.idTokenParsed.name;
console.log(authenticatedUser);
}).error(function () {
window.location.reload();
});
I assume it would be fairly common that web apps need to retrieve current user info. Anyone knows why the above code didn't work?
Thanks.
<script src="http://localhost:8080/auth/js/keycloak.js" type="text/javascript"></script>
<script type="text/javascript">
const keycloak = Keycloak({
"realm": "yourRealm",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "yourRealm/keep it default",
"public-client": true,
"confidential-port": 0,
"url": 'http://localhost:8080/auth',
"clientId": 'yourClientId',
"enable-cors": true
});
const loadData = () => {
console.log(keycloak.subject);
if (keycloak.idToken) {
document.location.href = "?user="+keycloak.idTokenParsed.preferred_username;
console.log('IDToken');
console.log(keycloak.idTokenParsed.preferred_username);
console.log(keycloak.idTokenParsed.email);
console.log(keycloak.idTokenParsed.name);
console.log(keycloak.idTokenParsed.given_name);
console.log(keycloak.idTokenParsed.family_name);
} else {
keycloak.loadUserProfile(function() {
console.log('Account Service');
console.log(keycloak.profile.username);
console.log(keycloak.profile.email);
console.log(keycloak.profile.firstName + ' ' + keycloak.profile.lastName);
console.log(keycloak.profile.firstName);
console.log(keycloak.profile.lastName);
}, function() {
console.log('Failed to retrieve user details. Please enable claims or account role');
});
}
};
const loadFailure = () => {
console.log('Failed to load data. Check console log');
};
const reloadData = () => {
keycloak.updateToken(10)
.success(loadData)
.error(() => {
console.log('Failed to load data. User is logged out.');
});
}
keycloak.init({ onLoad: 'login-required' }).success(reloadData);
</script>
simple javascript client authentication no frameworks.
for people who are still looking...
Your code asks the Keycloak client library to initialize, but it doesn't perform a login of the user or a check if the user is already logged in.
Please see the manual for details: http://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter
What your probably want to do:
Add check-sso to the init to check if the user is logged in and to retrieve the credentials keycloak.init({ onLoad: 'check-sso' ... }). You might even use login-required.
Make sure that you register a separate client for the front-end. While the Java backend client is of type confidential (or bearer only), the JavaScript client is of type public.
You find a very minimal example here: https://github.com/ahus1/keycloak-dropwizard-integration/blob/master/keycloak-dropwizard-bearer/src/main/resources/assets/ajax/app.js
Alternatively you can register a callback for onAuthSuccess to be notified once the user information has been retrieved.
Once you use Keycloak in the front-end, you will soon want to look in bearer tokens when calling REST resources in the backend.
You might have solved the problem by this time. I hope this answer help rest of the people in trouble.
when you use JavaScript Adopter
Below javascript should be added in of html page.
<script src="http://localhost:8080/auth/js/keycloak.js"></script>
<script>
/* If the keycloak.json file is in a different location you can specify it:
Try adding file to application first, if you fail try the another method mentioned below. Both works perfectly.
var keycloak = Keycloak('http://localhost:8080/myapp/keycloak.json'); */
/* Else you can declare constructor manually */
var keycloak = Keycloak({
url: 'http://localhost:8080/auth',
realm: 'Internal_Projects',
clientId: 'payments'
});
keycloak.init({ onLoad: 'login-required' }).then(function(authenticated) {
alert(authenticated ? 'authenticated' : 'not authenticated');
}).catch(function() {
alert('failed to initialize');
});
function logout() {
//
keycloak.logout('http://auth-server/auth/realms/Internal_Projects/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri')
//alert("Logged Out");
}
</script>
https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter Reference Link.
Note : Read the comments for 2 methods of adding json credentials.

Using the Google API javascript in Cordova / Phonegap

I'm developing an application with Cordova and would like to save files in Googe Drive.
I've got success in login to Google, using the cordova-plugin-googleplus (https://github.com/EddyVerbruggen/cordova-plugin-googleplus). However I could not get the plugin returns to me accessToken or idToken so I could use with Google javascript API.
window.plugins.googleplus.login(
{
'scopes': 'https://www.googleapis.com/auth/drive.file profile',
'offline': true,
'webApiKey': ‘CODE’
},
function (obj) {
$scope.$apply(function() {
$scope.srcImage = obj.imageUrl;
$scope.NomeGoogle = obj.displayName;
});
},
function (msg) {
alert('Erro');
alert('error: ' + msg);
}
);
I tried using the code below, but returned me the following error:
"Uncaught gapi.auth2.ExternallyVisibleError: Invalid cookiePolicy"
gapi.load('auth2', function() {
gapi.auth2.init({
client_id: 'REVERSED_CLIENTID',
}).then(function(){
auth2 = gapi.auth2.getAuthInstance();
console.log(auth2.isSignedIn.get()); //now this always returns correctly
});
});
I managed to figure out the problem, why wasn´t getting the serverAuthCode from plugin.
It is necessary to create 2 credentials on the Google Developers Console. The 1st must be Android, this will be for the plugin and the 2nd should be a Web App, this is necessary to achieve serverAuthCode.
The code looks like this
window.plugins.googleplus.login(
{
'scopes': 'https://www.googleapis.com/auth/drive.file profile',
'offline': true,
'webApiKey': ‘REVERSED_CODE of Web App Credential’
},
function (obj) {
$scope.$apply(function() {
$scope.srcImage = obj.imageUrl;
$scope.NomeGoogle = obj.displayName;
});
var data = $.param({
client_id: 'REVERSED_CODE of Web App Credential',
client_secret: 'SECRET_CODE of Web App Credential',
grant_type: 'authorization_code',
code: obj.serverAuthCode
});
var config = {
headers : {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
}
}
$http.post("https://www.googleapis.com/oauth2/v3/token", data, config).success(function(data, status) {
//data.access_token;
/** from now you can do use of google API **/
})
.error(function(data, status) {
console.log(data);
console.log(status);
});
},
function (msg) {
alert('Erro');
alert('error: ' + msg);
}
);
Thank you for your reply rojobo
At first I was hoping to skip the need for cordova-plugin-googleplus and just use the gapi within Cordova/PhoneGap to handle authentication with Google, but it appears the gapi client authentication may not work within cordova's file:// protocol.
The answer from #Joao sent me in the right direction, but I kept getting the Invalid cookiePolicy error when trying to use the gapi after retrieving the access_token (this was because I was ignoring step #2 listed below, and after authenticating with the plugini I was mistakenly trying to authenticate again with the gapi).
There is a step (#3 mentioned below) that was unclear to me. To authenticate with Google and then use the gapi in Cordova/PhoneGap, this worked instead...
use the cordova-plugin-googleplus to take care of the authentication and access token retrieval, do not use the gapi at all here
load the gapi client library (skip over the gapi.client.init() call and all the normal gapi authentication procedures)
Attach the access token we got from the plugin to the gapi client, and then make your gapi calls as needed
Step #3 took some digging for me to find, and meant I needed to add the access_token
gapi.client.setToken({access_token:'abc123456xyz'});
Once the access token was attached to the gapi client, I could use the gapi within Cordova/Phonegap:
// Load the YouTube API.
gapi.client.load('youtube', 'v3', function() {
// Do stuff...
};
try
window.plugins.googleplus.login(
{
'scopes': 'https://www.googleapis.com/auth/drive.file profile',
'offline': true,
'cookiepolicy': 'none',
'webApiKey': ‘CODE’
}

Categories

Resources