Authenticating using OfficeDev/office-js-helpers rather than adal - javascript

I'm working on an Office Add-in that currently uses adal to obtain an auth token.
As I want to use the Fabric front end I am changing it to React and I notice that the officer-js-helpers have implemented authenticators that seem to do the same job as the adal library.
Am I correct in this assumption? If so, how do I duplicate this adal config using the office-js-helpers authentication functions:
var adalConfig = {
instance: 'https://login.microsoftonline.com/',
tenant: 'myprivatesite.onmicrosoft.com',
clientId: 'xxx-xxx-xxx-xxx-xxx',
endpoints: {
'https://my.private.url.endpoint/path': 'https://myprivatesite.onmicrosoft.com/path.to.something',
}
And this token request:
var authContext = new AuthenticationContext(adalConfig);
authContext.acquireToken('https://myprivatesite.onmicrosoft.com/path.to.something', function (error, token) {
console.log(token)
});
UPDATE:
I have got the adal.js library working in my react app. I have used some of the code from the init function in the adalAuthenticationService angular provider to retrieve the authentication token.
So the question remains. Can I use the office-js-helpers to do the same thing?

Adal.js cannot be used out of the box for web add-ins authentication because within the sandboxed iFrame context of a web add-ins you cannot navigate simply to the authentication login page hosted outside your domain.
Office-js-helpers uses the dialogAPI when available and a popup as a fallback solution when not available.
If I remember correctly Office-js-helpers targets only Azure AD v2.0 (which comes with a lot of nice new features comparing to Azure AD). I guess it is a good choice.
I created an Open source sample the documentation can be interesting to you. However, this is not exactly what you want it is based on an AuthorizationCode flow while you are looking for Implicit flow.

OK It appears it is extremely easy. All that is required from the adal configuration is the client Id and the tenant.
if (OfficeHelpers.Authenticator.isAuthDialog()) {
return;
}
var authenticator = new OfficeHelpers.Authenticator();
authenticator.endpoints.registerAzureADAuth('xxx-xxx-xxx-xxx-xxx', //clientId
'myprivatesite.onmicrosoft.com' // tenant
);
authenticator.authenticate(OfficeHelpers.DefaultEndpoints.AzureAD)
.then(function (token) {
console.log(token);
.catch(function(error) {
console.log(error);
});

Related

How to call Google Drive API from Cloud Function for Firebase on behalf of a user?

I want to interact with the Google's Drive API from a Cloud Function for Firebase. For authentication / authorization, I am currently relying on getClient, which I believe uses the Service Account exposed in the Cloud Function environment:
import { google } from 'googleapis';
// Within the Cloud Function body:
const auth = await google.auth.getClient({
scopes: [
'https://www.googleapis.com/auth/drive.file',
],
});
const driveAPI = google.drive({ version: 'v3', auth });
// read files, create file etc. using `driveAPI`...
The above approach works, as long as target directories / files list the email address of the service account as an editor.
However, I'd like to interact with the Drive API on behalf of another user (which I control), so that this user becomes (for example) the owner of files being created. How can I achieve this?
To set the user you want to deligate as in your code just add a subject to the client, with user being the email of the user on your workspace domain.
const client = await auth.getClient();
client.subject = user;
I was able to achieve calling the Drive API on behalf of another user thanks to the suggestions made by #DalmTo.
The first step is to configure domain-wide delegation of authority in Google Workspace for the default AppEngine Service Account.
Next, the code in my question can be extended to receive a subject with the email of the user to impersonate via the clientOptions:
import { google } from 'googleapis';
// Within the Cloud Function body:
const auth = await google.auth.getClient({
scopes: [
'https://www.googleapis.com/auth/drive.file',
],
clientOptions: {
subject: 'email#to.impersonate',
},
keyFile: './serviceAccountKey.json',
});
const driveAPI = google.drive({ version: 'v3', auth });
// read files, create file etc. using `driveAPI`...
Now, the truly odd thing is that this only works when also passing the service account key via the keyFile option in addition. I.e., relying on the key being automatically populated (as it is in Cloud Functions for Firebase) does NOT work when also trying to impersonate a request. There are ongoing discussions of this bug on GitHub, specifically see this comment.
To make domain-wide delegation work without having to provide the keyFile option (which will likely require you to manage the sensitive key file in some way), another option is to sign a JWT and use it to obtain an Oauth token. The approach is outlined by Google, and I found this SO answer providing a code-example very helpful.

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

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

REST API with Google Firebase Authentication & Functions using Bearer Token

Quick Background: I'm programming an API that is thought to be used "standalone" i.e. there is no frontend involved. API access should be possible directly from e.g. Postman or Curl with a Bearer token in the Authentication Header.
I was looking at Google Firebase and thought it is probably a really good fit because all of the authentication is already "builtin" and directly compatible with Google Cloud Functions.
However after a weekend of experimenting I can not seem to figure out how to implement an REST API (With Google Cloud Functions) where the User can (In an web-interface) request an API token to interact with the API.
I don't want to handle authentication myself. I really would love to use the Firebase authentication for the API.
Here is what the final process should look like:
User logs into an web-interface with the standard Firebase Authentication process.
User clicks on something like "Request API Key" and gets a key shown in the web-interface (e.g. abc...). that is generated by Firebase Authentication.
User can make requests with e.g. curl to the API Hosted in Google Cloud Functions and just has to set the Authorization Header (Bearer abc...) and the "validation" of that token is handled by Firebase Authentication.
Here is what I already tried to generate the token:
admin.auth().createCustomToken(uid)
.then(function(customToken) {
console.log(customToken);
})
.catch(function(error) {
console.log('Error creating custom token:', error);
})
And then set the Token logged to the console in Postman as Bearer Token, and then use the following function to verify the token:
const authenticate = async (req, res, next) => {
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
res.status(403).send('Unauthorized');
return;
}
const idToken = req.headers.authorization.split('Bearer ')[1];
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.user = decodedIdToken;
next();
return;
} catch(e) {
console.log(e);
res.status(403).send('Unauthorized');
return;
}
}
Then I get this error
message: 'verifyIdToken() expects an ID token, but was given a custom token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
I understand that if I would implement an web-interface I could grab the ID token from the devtools (?), but the token is then only valid 1 hour... What I need is a token that is valid "indefinitely" and can be generated and shown to the user.
I think I know that I have to use Custom Tokens somehow but can not figure out how to get them working... (https://firebase.google.com/docs/auth/admin/create-custom-tokens).
Thanks very much in advance everybody!
Best
Rick
You're trying to build an API management solution on top of Firebase and Cloud Functions. Custom tokens and ID tokens are not suitable for this purpose. Custom tokens are only meant to be used as a user authentication credential on end user devices, and ID tokens represent a successful auth response. Both types of tokens expire after an hour.
If you need long-lived, managed API keys, then you will have to implement them yourself. There's nothing built into Firebase that you can use out of the box. I once implemented such a solution as a prototype, where I generated a Firestore document each time a user signed in and requested an API key. Then I used the document ID as the API key, which I could validate in the Cloud Function.
const apiKey = req.headers.authorization.split('Bearer ')[1];
const doc = await admin.firestore().collection('apiKeys').doc(apiKey).get();
if (doc.exists) {
next();
}
I also had to implement some local API key caching to make this work efficiently.
You might be able to avoid some of this work by using a solution like Google Cloud Endpoints (https://cloud.google.com/endpoints), although I don't have any personal experience with that. Finally, also look at open source solutions like https://wso2.com/api-management/ that enable you to set up your own API key management and gateway.

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

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

Okta integration with JavaScript

I have been tasked with integrating an existing JavaScript application with Okta.
This application requires access to certain resources on Amazon's AWS API Gateway. The API-Gateway-generated SDK requires an access key and a secret access key for which we would prefer to use temporary credentials.
According to AWS documentation, an assertion is required to make a call to AWS's AssumeRoleWithSAML in order to retrieve temporary credentials.
I've tried the following Okta resources, but have been unable to determine how to obtain an assertion using JavaScript:
AWS SAML Integration with Okta:
This setup allows for logging in to the AWS Console.
The Okta Sign-in Widget: Using the widget, I've been unable to find an assertion in what is being returned.
Okta API/SDK: A JavaScript API/SDK is not listed.
I have found a few Python implementations that return temporary access keys, but haven't found any examples or documentation describing a JavaScript approach with Okta.
Thank you.
Here is an example application showing how to integrate Okta with Amazon S3 in JavaScript, using the Okta Sign-In Widget: https://github.com/okta/okta-oidc-aws
This example application is based on Amazon's JavaScript in the Browser sample application, but authenticates against Okta using OpenID Connect instead of using Facebook, as Amazon's sample application does.
Please note that the current version of the Okta Sign-In Widget (1.7.0) includes the xhr library which conflicts with Amazon's JavaScript SDK. This will be fixed in version 1.9.0 of the Okta Sign-In Widget, which removes xhr. In the meantime, the example in the okta-oidc-aws repo ships with a custom version of the widget that has xhr disabled.
The GitHub repository for the okta-oidc-aws sample has full details on getting the example working.
At a high level, the important parts are as follows:
Get an OpenID Connect id_token from Okta.
Use the WebIdentityCredentials class to exchange the Okta id_token for an AWS IAM Role. This is known as "Web Identity Federation".
The code that does this is below, and is copied straight from the sample.html file in the example:
AWS.config.credentials = new AWS.WebIdentityCredentials({
RoleArn: AWS_ROLE_ARN,
WebIdentityToken: res.idToken
});
AWS.config.credentials.get(function(err) {
if (err) {
console.log("Error creating AWS Web Identity: " + err);
return;
}
bucket = new AWS.S3({
params: {
Bucket: AWS_S3_BUCKET_NAME
}
});
oktaLoginContainer.style.display = 'none';
uploadDialog.style.display = 'block';
listObjs();
});

Categories

Resources