How to use graph api on outlook add-ins? - outlook

According to documentation I created simple authentication in my outlook add-in https://learn.microsoft.com/en-us/graph/tutorials/javascript?tabs=aad&tutorial-step=3
But i get an error: DeviceCodeCredentials is not supported in the browser; when i try to create auth provider in my outlook add-in. Which authentication I should use to successfully create 'graph client instance'? Or better solutions exists?
`
const graph = require('#microsoft/microsoft-graph-client');
const authProviders =
require('#microsoft/microsoft-graph-client/authProviders/azureTokenCredentials');
_deviceCodeCredential = new azure.DeviceCodeCredential({
clientId: settings.clientId,
tenantId: settings.tenantId,
userPromptCallback: deviceCodePrompt
});
const authProvider = new authProviders.TokenCredentialAuthenticationProvider(
_deviceCodeCredential, {
scopes: settings.graphUserScopes
});
_userClient = graph.Client.initWithMiddleware({
authProvider: authProvider
});
`
I also tried other auth methods, but all they didn't work. How I can auth my user in outlook add in? Maybe there are some ways to auth without azure?

Related

Azure/Msal authentication inside PowerApp Component Framework returns AADSTS50177 error

I created a simple PowerApps Component Framework using the pac pcf init command.
After successfully packaging and importing this skeleton PCF application to my demo tenant I tried to add MSAL authentication to it.
I used the #azure/msal npm package to write a typescript configuration and login without adding React or Angular npm packages. I only used #azure/msal and package added during the pcf create process.
The final goal was to use the token received from the msal authentication make a request on a authorized method in my Wep Api.
The problem is that my Web Api is not located in my demo tenant and the user that is used for msal authentication is from the demo tenant and does not exist on the tenant of my Web Api.
I cannot change the login user in the popup window as it only displays the error message, and the guest user that was added to the demo tenant, that has access to the Web API cannot have Certificates added to it through portal azure or portal office admin center pages.
This is my login configuration(I will omit the tenant names and client id for the work tenant):
import { AuthenticationParameters, Configuration, UserAgentApplication } from '#azure/msal';
import { AuthOptions, CacheOptions, FrameworkOptions } from "#azure/msal/lib-commonjs/Configuration";
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
{
// Add control initialization code
const auth: AuthOptions = {
clientId:'clientid',
authority:'https://login.microsoftonline.com/tenantid',
redirectUri:'redirect uri',
validateAuthority: true
};
const cache: CacheOptions = {
cacheLocation:"localStorage"
};
const framework: FrameworkOptions = {
protectedResourceMap: new Map([
['web api url',['https://tenantid/clientid/uniquename (scope)']],
['CRM work sandbox',['CRM work sandbox user impersonation permission(scope)']]
]),
unprotectedResources:[]
};
const config: Configuration = {
auth: auth,
cache: cache,
framework: framework
};
const params: AuthenticationParameters = {
authority: 'https://login.microsoftonline.com/tenantid',
scopes:['offline_access',
'https://tenantid/clientid/uniquename(scope)',
'CRM work sandbox user impersonation permission(scope)'],
redirectUri:'web api redirect uri'
};
const userAgentApplication = new UserAgentApplication(config);
const login = userAgentApplication.loginPopup(params).then(data => {
console.log(data);
let user = userAgentApplication.getAccount();
console.log(user);
if (user) {
// signin successful
console.log('success');
} else {
// signin failure
console.log('fail');
}
}, function (error: string) {
// handle error
console.log('Error' + error);
});
}
The error message displayed:
AADSTS50177: User account 'user name' from identity provider
'https://sts.windows.net/guid/' does not exist in tenant 'name'
and cannot access the application 'client id'(name of registered
app in portal azure) in that tenant. The account needs to be
added as an external user in the tenant first. Sign out and
sign in again with a different Azure Active Directory user account.
Is there a way to test this without adding the pcf or account in my work tenant ?

Accessing Google API from aws lambda : Invalid OAuth scope

I am still struggling with Google's terminology of apis and services but my goal is to have automated functions via aws lambda which act on a G Suite Account (domain?) or more specific on users of this domain.
For now I just want to list all users of that domain. I run this code locally for testing.
What I have done:
I created a service account
I downloaded the json key file which contains the private key, private key id and so on
I enabled G Suite Domain-wide Delegation.
I delegated domain-wide authority to the service account from the GSuite Account
I added the following scopes for the client in the GSuite Admin Console:
https://www.googleapis.com/auth/admin.directory.group
https://www.googleapis.com/auth/admin.directory.user
This is the implementation:
const { google } = require("googleapis");
const auth = new google.auth.GoogleAuth({
keyFile: "credentials.json",
scopes:
"https://www.googleapis.com/auth/drive.readonly,https://www.googleapis.com/admin/directory/v1, https://www.googleapis.com/auth/admin.directory.group, https://www.googleapis.com/auth/admin.directory.user",
});
const service = google.admin({ version: "directory_v1", auth });
service.users.list(
{
domain: "my.domain.com",
maxResults: 10,
orderBy: "email",
},
(err, res) => {
if (err) return console.error("The API returned an error:", err.message);
const users = res.data.users;
if (users.length) {
console.log("Users:");
users.forEach((user) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log("No users found.");
}
}
);
I am not sure why I have to add the scopes in the GoogleAuth object but I took this from the google documentation.
When I run this I get the following error:
The API returned an error: invalid_scope: Invalid OAuth scope or ID token audience provided.
The Directory API can only be used by admins
A Service account is not an admin
If the service account shall act on behalf on the admin, you need to
enable G Suite Domain-wide Delegation (as you already did)
impersonate the service account as the admin by setting the user to be impersonated
In general, when you are using a service account you need to build the authentication flow, as explained in the documentation, that is you need to create JSON Web Token (JWT) specifying the user to impersonate.
A sample code snippet for Javascript:
const jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
scopes,
user // User who will be impersonated (needs to be an admin)
);
await jwtClient.authorize();
return jwtClient;

How to use passport-local with graphql

I'm trying to implement GraphQL in my project and I would like to use passport.authenticate('local') in my login Mutation
Code adaptation of what I want:
const typeDefs = gql`
type Mutation {
login(userInfo: UserInfo!): User
}
`
const resolvers = {
Mutation: {
login: (parent, args) => {
passport.authenticate('local')
return req.user
}
}
Questions:
Was passport designed mostly for REST/Express?
Can I manipulate passport.authenticate method (pass username and password to it)?
Is this even a common practice or I should stick to some JWT library?
Passport.js is a "Express-compatible authentication middleware". authenticate returns an Express middleware function -- it's meant to prevent unauthorized access to particular Express routes. It's not really suitable for use inside a resolver. If you pass your req object to your resolver through the context, you can call req.login to manually login a user, but you have to verify the credentials and create the user object yourself before passing it to the function. Similarly, you can call req.logout to manually log out a user. See here for the docs.
If you want to use Passport.js, the best thing to do is to create an Express app with an authorization route and a callback route for each identify provider you're using (see this for an example). Then integrate the Express app with your GraphQL service using apollo-server-express. Your client app will use the authorization route to initialize the authentication flow and the callback endpoint will redirect back to your client app. You can then add req.user to your context and check for it inside resolvers, directives, GraphQL middleware, etc.
However, if you are only using local strategy, you might consider dropping Passport altogether and just handling things yourself.
It took me a while to wrap my head around the combination of GraphQL and Passport. Especially when you want to use the local strategy together with a login mutation makes life complicated. That's why I created a small npm package called graphql-passport.
This is how the setup of the server looks like.
import express from 'express';
import session from 'express-session';
import { ApolloServer } from 'apollo-server-express';
import passport from 'passport';
import { GraphQLLocalStrategy, buildContext } from 'graphql-passport';
passport.use(
new GraphQLLocalStrategy((email, password, done) => {
// Adjust this callback to your needs
const users = User.getUsers();
const matchingUser = users.find(user => email === user.email && password === user.password);
const error = matchingUser ? null : new Error('no matching user');
done(error, matchingUser);
}),
);
const app = express();
app.use(session(options)); // optional
app.use(passport.initialize());
app.use(passport.session()); // if session is used
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => buildContext({ req, res, User }),
});
server.applyMiddleware({ app, cors: false });
app.listen({ port: PORT }, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
});
Now you will have access to passport specific functions and user via the GraphQL context. This is how you can write your resolvers:
const resolvers = {
Query: {
currentUser: (parent, args, context) => context.getUser(),
},
Mutation: {
login: async (parent, { email, password }, context) => {
// instead of email you can pass username as well
const { user } = await context.authenticate('graphql-local', { email, password });
// only required if express-session is used
context.login(user);
return { user }
},
},
};
The combination of GraphQL and Passport.js makes sense. Especially if you want to add more authentication providers like Facebook, Google and so on. You can find more detailed information in this blog post if needed.
You should definitely use passport unless your goal is to learn about authentication in depth.
I found the most straightforward way to integrate passport with GraphQL is to:
use a JWT strategy
keep REST endpoints to authenticate and retrieve tokens
send the token to the GraphQL endpoint and validate it on the backend
Why?
If you're using a client-side app, token-based auth is the best practice anyways.
Implementing REST JWT with passport is straightforward. You could try to build this in GraphQL as described by #jkettmann but it's way more complicated and less supported. I don't see the overwhelming benefit to do so.
Implementing JWT in GraphQL is straightforward. See e.g. for express or NestJS
To your questions:
Was passport designed mostly for REST/Express?
Not in principle, but you will find most resources about REST and express.
Is this even a common practice or I should stick to some JWT library?
Common practice is to stick to JWT.
More details here: OAuth2 in NestJS for Social Login (Google, Facebook, Twitter, etc)
Example project bhere: https://github.com/thisismydesign/nestjs-starter

Invalid signature while validating Azure ad access token, but id token works

I am getting invalid signature while using jwt.io to validate my azure ad access token. My id token, however, validates just fine!
I have seen and tried the solutions suggested in
Invalid signature while validating Azure ad access token
and
https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx
but neither works for my access token.
The access and Id token is generated via Adal.js:
var endpoints = {
"https://graph.windows.net": "https://graph.windows.net"
};
var configOptions = {
tenant: "<ad>.onmicrosoft.com", // Optional by default, it sends common
clientId: "<app ID from azure portal>",
postLogoutRedirectUri: window.location.origin,
endpoints: endpoints,
}
window.authContext = new AuthenticationContext(configOptions);
Why can I validate my ID token, but not my access token?
Please refer to thread : https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609
but if look at the Jwt.Header you will see a 'nonce'. This means you need special processing. Normal processing will fail.
So if nonce includes in access token , validate signature with JWT.io or JwtSecurityToken won't success .
If anyone else has invalid signature errors, you should check this comment : https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/521#issuecomment-577400515
Solved the issue for my configuration.
Essentially, if you are getting access tokens to access your own resource server and not the Graph API, your scopes parameter should be [CLIENT_ID]/.default (and if you are using the access token to access the Graph API, you don't need to validate the token yourself)
Thanks to Nan Yu I managed to get token that can be validated by any public jwt validator like jwt.io
(couldn't put my comment in the comments section under Nan Yu's answer because its too long).
So as I understand the point from the discussion mentioned by Nan Yu that by default Azure AD generates tokens for Microsoft Graph and these tokens use special signing mechanism so that it is not possible to validate signature using public validators (except jwt.ms Microsoft's validator which most probably knows what mysterious special handling means :) ).
To get access token not for Microsoft Graph that can be validated using public validators I had to:
Remove any Microsoft Graph related scopes (by default I had only one scope configured User.Read so removed it in appConfig > API permissions)
create a custom scope for your application (appConfig > Expose an API > Add scope ...) this scope will look like api://{application-id}/scope-name
add just created scope in the application API permissions (appConfig > API permissions > Add api permission > My APIs > select your application > Delegated Permissions > Check your scope > Add permission)
then use this scope in your openid client scopes, in my case I have: openid offline_access {application-id}/scope-name
Note that in the openid client config newly created scope is used without api:// prefix (offline_access I have to enable refresh_token can be ignored if refresh token mechanism is not used)
Well thanks to #Antoine I fix my code. Here I will let my personal vue.js plugin that is working for everybody else reference:
import { PublicClientApplication } from '#azure/msal-browser'
import { Notify } from 'quasar'
export class MsalService {
_msal = null
_store = null
_loginRequest = null
constructor (appConfig, store) {
this._store = store
this._msal = new PublicClientApplication(
{
auth: {
clientId: appConfig.auth.clientId,
authority: appConfig.auth.authority
},
cache: {
cacheLocation: 'localStorage'
}
})
this._loginRequest = {
scopes: [`${appConfig.auth.clientId}/.default`]
}
}
async handleResponse (response) {
await this._store.dispatch('auth/setResponse', response)
const accounts = this._msal.getAllAccounts()
await this._store.dispatch('auth/setAccounts', accounts)
if (accounts.length > 0) {
this._msal.setActiveAccount(accounts[0])
this._msal.acquireTokenSilent(this._loginRequest).then(async (accessTokenResponse) => {
// Acquire token silent success
// Call API with token
// let accessToken = accessTokenResponse.accessToken;
await this._store.dispatch('auth/setResponse', accessTokenResponse)
}).catch((error) => {
Notify.create({
message: JSON.stringify(error),
color: 'red'
})
// Acquire token silent failure, and send an interactive request
if (error.errorMessage.indexOf('interaction_required') !== -1) {
this._msal.acquireTokenPopup(this._loginRequest).then(async (accessTokenResponse) => {
// Acquire token interactive success
await this._store.dispatch('auth/setResponse', accessTokenResponse)
}).catch((error) => {
// Acquire token interactive failure
Notify.create({
message: JSON.stringify(error),
color: 'red'
})
})
}
})
}
}
async login () {
// this._msal.handleRedirectPromise().then((res) => this.handleResponse(res))
// await this._msal.loginRedirect(this._loginRequest)
await this._msal.loginPopup(this._loginRequest).then((resp) => this.handleResponse(resp))
}
async logout () {
await this._store.dispatch('auth/setAccounts', [])
await this._msal.logout()
}
}
// "async" is optional;
// more info on params: https://quasar.dev/quasar-cli/boot-files
export default ({
app,
store,
Vue
}) => {
const msalInstance = new MsalService(
app.appConfig, store
)
Vue.prototype.$msal = msalInstance
app.msal = msalInstance
}
PD: using quasar framework
If you are using msal.js library with react, add this to your auth configuration.
scopes: [`${clientId}/.default`]
Editing scopes fixed issue for me

Bot framework v3 unauthorized

I'm trying to use MS botframework V3 to create a basic bot using the nodejs tutorial code, but I keep getting 401 Unauthorized using the emulator. Please help?
AppId / Secret are set in env variables and definitely correct in emulator.
Code below
var restify = require('restify');
var builder = require('botbuilder');
//=========================================================
// Bot Setup
//=========================================================
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat bot
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
var bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());
//=========================================================
// Bots Dialogs
//=========================================================
bot.dialog('/', function (session) {
session.send("Hello World");
});
try changing the appId and appSecret to MicrosoftAppId and MicrosoftAppPassword respectively
As stated on botframework website
In V1, the authentication properties were stored with these keys:
AppId
AppSecret
In V3, to reflect changes to the underlying auth model, these keys have been changed to:
MicrosoftAppId
MicrosoftAppPassword
Edit:
So, from a post on github by Steven the actual values are
appId
appPassword
These values won't work with the emulator due to an issue with node sdk however they should work when deployed.
Link:
https://github.com/Microsoft/BotBuilder/issues/625
While using the emulator for the first time, i was giving an appid and password on my own. I learnt that no app id and no password also works fine in local.
:Removing the appid and password in the web.config in the bot application and in the emulator resolved my error. Hope it helps. Cheers!
I am having the same the issue, but I can get the above solution to work. It appears to suggest, you can put in any values for id and pw. If they are not found, then authorization is turned off? Is this correct?
I tried this
var connector = new builder.ChatConnector({appId:'999', appPassword: 'xxxx'});
And I still get a 401.

Resources