Teams Icon in Microsoft Teams Configurable Tab - microsoft-graph-api

I am building a teams app which is using a non-AAD based service to Authenticate. I would like to get the Teams Icon in my Configurable Tab. What are the option I have? I can not register an app, as the Teams app is going to be deployed on different tenants. Can I use microsoftTeams.authentication.getAuthToken for graph api(/teams/${teamsid}/photo/$value) without registering an app but on behalf of user ? or is there any easier way to get the teams icon.
const authTokenRequest: microsoftTeams.authentication.AuthTokenRequest = {
successCallback: function (token: string) {
//const decoded: { [key: string]: any; } = jwt.decode(token);
//localStorage.setItem("name", decoded.name);
localStorage.setItem("token", token);
const response = await axios.get(apiConfig.endpoint + "/api/" + functionName, {
headers: {
authorization: "Bearer " + accessToken?.token || "",
},
});
return response.data;
},
failureCallback: function (error: any) {
console.log("Failure on getAuthToken: " + error);
}
};
microsoftTeams.initialize(() => {
microsoftTeams.getContext((r) => {
microsoftTeams.authentication.getAuthToken(authTokenRequest);
});
});
Through this I get below error:
Attempting to handle auth response: error:invalid_resource|AADSTS500011: The resource principal named api://xxxxx/botid-xxxx was not found in the tenant named xxxx. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant., resource:api://xxxxx/botid-xxxx, error mapped to action:resourceDisabled

You must have your app registered to be able to use graph API. It is absolutely no problem that the app will be installed on a different tenant, you just need to select "multitenant" option when registering the app to enable this scenario.
As far as I understand, logo pictures / icons are considered company data, so you need user consent to get it. For this reason, the app needs to be registered, and the user (or user admin) must agree to give the app access to the team info when adding your app.

Related

Custom activity feed notifications sent from daemon(nodejs app), not getting the data assigned to subEntityId in teams mobile client

I'm using activityFeedNotification graph api to send push notification to the users of our teams tab app from backend using nodejs. The notification is sending successfully in both teams desktop and mobile client but we're not getting the data assigned to subEntityId in mobile client(In desktop client and browser we're getting it).
We are encoding the data(object) and assigning it to the subEntityId in teams context in our nodejs application. Then in teams client, we get that data from teams context using microsoft teams sdk and redirect user to the respective page in our application based on whatever data we get in subEntityId
In desktop, deeplinking is working perfectly but in android client, we're not getting any data in subEntityId. It is just opening the homepage of our tab app but I need to redirect user to specific page based whatever data is assigned to subEntityId.
Below I've provided how we're encoding the data and assigning it to subEntityId.
Server Side:
const context = encodeURIComponent(
JSON.stringify({
"subEntityId": {
"type": "PROGRAM_PROFILE",
"program_id": "12345",
uid: uuidv4(),
}
})
);
const body = {
topic: {
source: 'text',
value: notificationTopic,
webUrl: `https://teams.microsoft.com/l/entity/${TEAMS_APP_ID}/index?context=${context}`,
},
activityType: 'commonNotification',
previewText: {
content: notificationSubtitle,
},
templateParameters: [
{
name: 'title',
value: notificationTitle,
},
],
};
const url = `https://graph.microsoft.com/v1.0/users/${userId}/teamwork/sendActivityNotification`;
await axios.post(url, body));
Client Side:
const context = await app.getContext();
console.log(context?.page?.subPageId); // getting undefined
Any kind of help is appreciated!
From the documentation:
{page.subPageId}: The developer-defined unique ID for the subpage this content points defined when generating a deep link for a specific item within the page. (Known as {subEntityId} prior to TeamsJS v.2.0.0).
You are using subEntityId on the backend but accessing subPageId on the client side.

Either scp or roles claim need to be present in the token using when application permissions to read sharepoint sites

I created an app in Azure and set it up to use Access and ID tokens.
I want to connect to different tenants and read SharePoint sites. Here are the permissions I've requested and received Admin Consent for:
For now, I have set up an App Secret but I do plan to move to a certificate later.
I have this code to get the access token and I do get an access token back:
const params = new URLSearchParams();
params.append("grant_type", "client_credentials");
params.append("scope", "https://graph.microsoft.com/.default");
params.append("client_id", process.env.client_id);
params.append("client_secret", process.env.client_secret);
var url = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`;
const response = await fetch(url,
{
method: 'POST',
body: params,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
However when I try to read the root site below
var url = "https://graph.microsoft.com/v1.0/sites?search=*";
const response = await fetch(url,
{
method: 'GET',
headers: { 'Authorization': `Bearer ${access_token}` }
}
);
I get this error:
error: {
code: 'AccessDenied',
message: 'Either scp or roles claim need to be present in the token.',
innerError: {
'request-id': 'ec47913f-2624-4d1c-9b27-5baf05ccebfd',
date: '2019-08-16T14: 15: 37'
}
}
I checked the token at https://jwt.io/ and indeed I do not see any entry for roles or scp.
It looks like I missed a step but I cannot figure out which step.
I am getting the token like this:
https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token
What am I doing incorrectly?
The first thing to understand is that you cannot receive both Application and Delegated permissions in the same token, it is an either/or scenario. Which type you receive depends entirely on which OAuth Grant you used to request the token:
Authorization Code and Implicit return Delegated tokens with an scp property
Client Credentials return Application tokens with a roles property
The second thing is that you've requested scopes to two different APIs. Based on what you've selected, you won't have access to SharePoint through the Microsoft Graph because you've only requested access to the legacy SharePoint API. More importantly, you've only requested the Delegated User.Read scope for Graph so when you use Client Credentials to obtain the token, that token won't have any permissions.
In order to obtain an Application token for reading SharePoint sites, you'll need Sites.Read.All Microsoft Graph Application permission selected.

Error "The OneDriveForBusiness for this user account cannot be retrieved." when accessing Microsoft OneNote with Graph API

I make the following REST GET request:
https://graph.microsoft.com/v1.0/me/onenote/notebooks
I get the following response:
{
"error": {
"code": "30108",
"message": "The OneDriveForBusiness for this user account cannot be retrieved.",
"innerError": {
"request-id": "25926552-3157-483a-bbcd-41a7105cd531",
"date": "2017-07-22T18:46:07"
}
}
}
I do not have a One Drive For Business account. Do I really need one to access the OneNote API?
Thanks.
Yes. In order to use the API (to access OneNote data), you must have a OneDrive (whether personal/consumer or business/Office 365) - since the OneNote cloud data is actually stored in OneDrive/SharePoint. If you have an Office 365 account, you can try going to https://portal.office.com and then click in the left-hand "waffle" button, and click OneDrive which should create your own personal OneDrive for Business.
Please take a look at https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/onenote for more details.
Also, if you are just trying out the API you could use Graph Explorer. It has some saved/sample queries that you can try. (Under Sample Queries, click show more samples and toggle the OneNote switch).
Hope this helps,
Here how I solved it in my Azure function by switching to authentication with Microsoft account and using the classic OneNote Rest API.
var request = require('request');
module.exports = function (context, req) {
var microsoftAccountAccessToken = req.headers['x-ms-token-microsoftaccount-access-token'];
context.log( "Microsoft Account Access Token: " + microsoftAccountAccessToken );
request(
{
url: 'https://www.onenote.com/api/v1.0/me/notes/notebooks',
method: "GET",
headers: {
'Authorization': 'Bearer ' + microsoftAccountAccessToken
},
},
function( error, response, body )
{
if (!error && response.statusCode === 200) {
context.log(body);
context.res = {
body: body
};
context.done();
}
else {
context.log("error: " + error)
context.log("response.statusCode: " + response.statusCode)
context.log("response.statusText: " + response.statusText)
context.res = {
body: response.statusText
};
context.done();
}
}
);
};
https://learn.microsoft.com/en-us/graph/onenote-error-codes#30108
The user's personal OneDrive for Business could not be retrieved. The following table lists some possible causes.
The user's personal site has not been provisioned. The user should open OneDrive for Business and follow any instructions to provision the site. If this fails, they should contact their Office 365 tenant administrator.
The user's personal site is currently being provisioned. Try the request later.
The user does not have a valid OneDrive for Business license. The user should contact their Office 365 tenant administrator.
A network issue prevented the request from being successfully sent.
I tried many ways and finally I used the method mentioned here: https://learn.microsoft.com/en-us/previous-versions/office/office-365-api/how-to/onenote-auth
The auth server is login.live.com, the above page provides two methods: code and token. Both could use. After auth and get the token, I can call Graph API with that token.
Code method is simpler to demonstrate. First, open this in browser:
https://login.live.com/oauth20_authorize.srf
?response_type=token
&client_id={client_id}
&redirect_uri={redirect_uri}
&scope={scope}
Then, after login an account, it will callback. Just copy the access_token in the callback URL. Do:
GET https://graph.microsoft.com/v1.0/me/onenote/pages
Accept: application/json
Authorization: Bearer {access_token}
The pages could be retrieved without 30108 error. These are simple test steps. I implemented in Java, and can get OneNote data through Microsoft's Graph library(com.microsoft.graph:microsoft-graph:1.5.+). As below:
IOnenotePageCollectionPage pages = graphClient.me().onenote().pages().buildRequest().get();
graphClient is IGraphServiceClient. But I implemented the authentication provider through login.live.com.

Get user's phone number in Firefox OS

Is there any way to fetch user’s phone number in Firefox OS?
If so, any help would be appreciated.
According to Mozilla's app permissions page, there is an permission called "phonenumberservice" but there is no information about it. Anyway, the permision is listed under the "Internal (Certified) app permissions", which means that, when available, it can only be used by "system-level apps and default apps created by Mozilla/operators/OEMs".
With Firefox 2.0 you should be able to use Mobile Identity API:
https://wiki.mozilla.org/WebAPI/MobileIdentity
https://bugzilla.mozilla.org/show_bug.cgi?id=1021594
I believe the permission is:
"permissions": {
"mobileid": {} }
And it is privileged.
So, as #Jason said, the Mobile Identity API provides this capability, and not just for certified, but for privileged applications. So it is no longer just for OEMs.
The Mozilla Wiki site shows the API:
dictionary MobileIdOptions {
boolean forceSelection = false;
};
partial interface Navigator {
Promise getMobileIdAssertion(optional MobileIdOptions options);
};
The site also provides a sample code skeleton for this:
function verifyAssertion(aAssertion) {
// Make use of the remote verification API
// and return the verified msisdn.
// NB: This is necessary to make sure that the user *really* controls this phone number!
}
// Request a mobile identity assertion and force the chrome UI to
// allow the user to change a possible previous selection.
navigator.getMobileIdAssertion({ forceSelection: true })
.then(
(assertion) => {
verifyAssertion(assertion)
.then(
(msisdn) => {
// Do stuff with the msisdn.
}
);
},
(error) {
// Process error.
};
);
For this to work, you need to add the mobileid permission in the manifest file, for example like this (I made up the description):
"permissions": {
"mobileid": {
"description": "Required for sending SMS for two factor authentication",
"access": "readonly"
}
}
PS: I made this answer, because most answers are outdated, and the one that isn't, does not contain all useful information.
References:
App Manifest Documentation
Firefox Remote Verification

Authentication for Node.js App with Angular.js and iOS Clients

I've tried to read as many different answers and posts as possible, but I still can't quite settle on a solution that fits my needs. I'm trying to work out the best (most efficient, but mostly more secure) way to handle user authentication, log in, etc.
I have a Node.js server, running on Express; I have an Angular.js web app; and I have an iOS app. I expose a RESTful API with Express/Node.js.
Cookies
The first things I read said to use cookies, and to store a session id/login token on the server side (hashed) and on the client side (unhashed). The client would transfer this id with each request, the server would hash it, parse it and process the request accordingly. This does not feel RESTful (not a huge issue), but more importantly, would I have to duplicate my API: one for username/password authentication (e.g. done via curl) and one for cookie-based authentication (e.g. my web app)?
Another problem with this: what I would do if I had multiple connections from the one user, e.g. they're logged in in two browsers, an iPhone and an iPad. Would my storage of their session ids need to now be an array?
HTTP Basic Auth
The next idea was to use HTTP Basic Auth (with SSL), which seems easy enough, but is not recommended because you need to transfer a username and password with each request. If I were to do it with HTTP Basic Auth, would I then store the username and password in cookies (or HTML local storage) to allow for 'Remember Me' functionality? Or could I combine the two: use HTTP Basic Auth for the actual requests (post a new post, etc.) and just use a session id stored in a cookie for the initial log in sequence/remember me aspects?
Is transmitting a session id more secure than just transmitting the user's password? How?
The session id is going to act ostensibly as a password, so to me transmitting it would have the same security issues as transmitting a password.
Basic Auth seems to be supported across all platforms, which is ideal. The main downside seems to be needing to transfer client authentication data with each request. Is there a way to mitigate this issue?
OAuth
OAuth seems like overkill for my needs. I think I would lose the ability to do curl commands to test my API. How is OAuth an improvement over the cookies method?
As you can probably tell, I'm a little confused by the diverse information available, so if you have a set of good links—applicable to this scenario—I would love to read them. I'm trying to find a solution that fits across all platforms, but is still as secure as possible. Also, if I have any of my terminology wrong, please correct me because it will make searching easier for me.
Thanks.
Update:
I've been thinking about this problem, and I've had an idea. Please tell me if this is dumb/insecure/any feedback, because I'm not sure if it's good.
When the user logs in, we generate a random session id (salted etc.). This optional session id is sent to the client, which the client can store (e.g. in cookies) if they choose; the session id is stored in the database.
This session id is then optionally sent with each request as either an HTTP Authentication header or query string, or the client can just send the username and password if they want (which gives us our regular REST API). At the server end, we check first for a session id parameter, if it's not present, we check for username/password. If neither are there—error.
On the server, we check that the session id is associated with the correct username. If it is, we complete the request.
Every time the user logs in, we create a new session id or delete the current one, and send this with the response to the log in request.
I think this lets me use the regular REST API, where appropriate, with Basic Auth, and maintain sessions/remember me functionality. It doesn't solve the multiple log ins issue, but otherwise I think this way should would. Please let me know.
I would use a token based authentication where you can send a token (automatically) with each request. You'll have to log in once, the server will provide you with a token which you can then use to send with each request. This token will be added to the HTML header, so that you don't have to modify each request to the browser.
You can set certain calls in the API so that they always need a token, while others might not be token protected.
For Express, you can use express-jwt (https://www.npmjs.org/package/express-jwt)
var expressJwt = require('express-jwt');
// Protect the /api routes with JWT
app.use('/api', expressJwt({secret: secret}));
app.use(express.json());
app.use(express.urlencoded());
If you want to authenticate you can create this function in your express server:
app.post('/authenticate', function (req, res) {
//if is invalid, return 401
if (!(req.body.username === 'john.doe' && req.body.password === 'foobar')) {
res.send(401, 'Wrong user or password');
return;
}
var profile = {
first_name: 'John',
last_name: 'Doe',
email: 'john#doe.com',
id: 123
};
// We are sending the profile inside the token
var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });
res.json({ token: token });
});
And for protected calls something that starts with /api:
app.get('/api/restricted', function (req, res) {
console.log('user ' + req.user.email + ' is calling /api/restricted');
res.json({
name: 'foo'
});
});
In your Angular application you can login with:
$http
.post('/authenticate', $scope.user)
.success(function (data, status, headers, config) {
$window.sessionStorage.token = data.token;
$scope.message = 'Welcome';
})
.error(function (data, status, headers, config) {
// Erase the token if the user fails to log in
delete $window.sessionStorage.token;
// Handle login errors here
$scope.message = 'Error: Invalid user or password';
});
And by creating an authentication interceptor, it will automatically send the token with every request:
myApp.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
}
return config;
},
response: function (response) {
if (response.status === 401) {
// handle the case where the user is not authenticated
}
return response || $q.when(response);
}
};
});
myApp.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
});
If you have to support old browsers which do not support local storage. You can swap the $window.sessionStorage with a library like AmplifyJS (http://amplifyjs.com/). Amplify for example uses whatever localstorage is available. This would translate in something like this:
if (data.status === 'OK') {
//Save the data using Amplify.js
localStorage.save('sessionToken', data.token);
//This doesn't work on the file protocol or on some older browsers
//$window.sessionStorage.token = data.token;
$location.path('/pep');
}
}).error(function (error) {
// Erase the token if the user fails to log in
localStorage.save('sessionToken', null);
// Handle login errors here
$scope.message = 'Error: Invalid user or password';
});
And the authintercepter we swap for:
angular.module('myApp.authInterceptor', ['myApp.localStorage']).factory('authInterceptor', [
'$rootScope',
'$q',
'localStorage',
function ($rootScope, $q, localStorage) {
return {
request: function (config) {
config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + localStorage.retrieve('sessionToken');
return config;
},
response: function (response) {
if (response.status === 401) {
}
return response || $q.when(response);
}
};
}
]);
You can find everything except AmplifyJS in this article:
http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/
Have a look to the yeoman generator for angular and node? The generator-angular-fullstack have a very nice structure for user authentification using passport.
You can see an example here :
the code: https://github.com/DaftMonk/fullstack-demo
the result: http://fullstack-demo.herokuapp.com/
Hope it helps!
I use generator-angular-fullstack, the /api services are not secured, get your _id from /api/users/me, logout, and go to /api/users/your_id_here, you will figure out that the /api not secured.

Resources