Im still battleing with trying to pull down the details of the logged in (Outlook) user within the add in im developing.
I have exploered the EWS service but, as far as I cant tell, the functions I need are not available so I have started looking at the Outlook REST API.
I can get very basic user details with the following call:
function sendFindPersonRequest() {
//get auth token for Graph
Office.context.mailbox.getCallbackTokenAsync({ isRest: true }, function (result) {
if (result.status === "succeeded") {
var accessToken = result.value;
console.log('REST Access Token : ' + accessToken);
// Use the access token.
getCurrentItem(accessToken);
} else {
// Handle the error.
}
});}
function getCurrentItem(accessToken) {
// Construct the REST URL to the current item.
// Details for formatting the URL can be found at
// https://learn.microsoft.com/previous-versions/office/office-365-api/api/version-2.0/mail-rest-operations#get-messages.
var getMessageUrl = Office.context.mailbox.restUrl +
'/v2.0/me/';
$.ajax({
url: getMessageUrl,
dataType: 'json',
headers: { 'Authorization': 'Bearer ' + accessToken }
}).done(function (item) {
// Message is passed in `item`.
var subject = item.displayName;
console.log('item subject: ' + subject);
}).fail(function (error) {
// Handle error.
});
}
But this doesnt pass any more than Alias, DisplayName and Emailaddress.
#odata.context: "https://outlook.office365.com/api/v2.0/$metadata#Me"
#odata.id: "https://outlook.office365.com/api/v2.0/Users('')"
Alias: "Joe.Bloggs"
DisplayName: ""
EmailAddress: "Joe.Bloggs#emailhere.co.uk"
Id: "baf52ae4-............"
MailboxGuid: "257f3fe1-6.............."
Im looking to get extendeddetails such as Jobtitle, OfficeAddress etc (which are standard AD fields).
I have looked at the GetUser method as well but that returns the same. I really dont want to go down Graph route and it feels that I am missing something as really expect those other fields to be there.
Has any one else used this to better affect?
Thanks
I didn't see where we can create extensions for a user based on Office 365 Data Extensions and Outlook Extended Properties.
And Microsoft.OutlookServices.User also doesn't have such properties as Jobtitle, OfficeAddress etc.
When I tried to use the OData $select query parameter like:
https://outlook.office365.com/api/v2.0/me?$select=DisplayName,Jobtitle,OfficeAddress
It will give an error: "Could not find a property named 'Jobtitle' on type 'Microsoft.OutlookServices.User'."
I'm afraid that what you want is not available in Outlook REST API. You should use Microsoft Graph API or AAD Graph API instead.
Update:
We can use GET https://outlook.office365.com/api/v2.0/me/people?$search="" to find the person's information.
Related
I've been stuck on this issue for a while now, I'm using ADAL.js on the front-end to handle login and authentication. Once logged in I need to get the info for the user (roles, groups, name etc...) however I can't get anything back from the /adfs/userinfo endpoint other than a 401.
So far I log the user in and get back an id_token and access token (or "adal.access.token.key{guid}" in the browser) which is identical to the access key. Due to a cors issue on the front-end I then send this to a back-end mvc core 2.2 controller to make the call to /adfs/userinfo which is where I get the 401. Javascript code below
this.adalAuthentication.Instance.acquireToken(this.adalAuthentication.Instance.config.clientId, (error, token) => {
if (error || !token) {
console.log('ADAL Error Occurred: ' + error);
return;
}
axios({
method: 'get',
url: '/identity/completeLogin/' + token,
headers: {
'Authorization': 'Bearer ' + token
}
}).then((response) => { console.log(response.data) });
});
And controller action...
[HttpGet("completeLogin/{access_token}")]
public async Task<HttpResponseMessage> CompleteLogin(string access_token)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth" + access_token);
var response = await client.GetAsync("https://adfs.server/adfs/userinfo");
response.EnsureSuccessStatusCode();
try
{
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
catch (Exception e)
{
throw(e);
}
}
At this point I'm stumped, I'm thinking I either can't use ADAL for this or perhaps need to use oidcinstead of OAuth/jwt but I don't want to have to rewrite lots just to find out that doesn't work either or there's a better/best practice way of doing it. Has anyone had this issue before and/or can point me in the right direction or can see where I'm going wrong?
Other things I've tried;
hitting adfs/oauth/token endpoint (just returns with adfs server error)
setting Authorisation on the backend to client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer" + access_token); (just returns invalid token).
Making the front and backend a post method and using the getCachedToken method on the ADAL AuthenticationContext
EDIT: I also have this question open with a the slightly more specific goal of getting an access token with the id token
There's a Postman sample here.
Be aware that "userinfo" only returns a "sub" claim.
I don't know if I'm just not looking in the right places, but I cannot seem to find the right guidance on where to begin working with React / .NET Core 2.1 Web API and (on-prem) Active Directory authentication.
I'm relatively new to .NET authentication in general, and completely new to Active Directory authentication.
I started by using the .NET Core 2.1 React template and attempting to add auth to it, but got completely lost.
Where do I even start?
For me, step one was to set up JWT authentication, such as described in this MSDN blog post.
Next, I had to find a library to use to check a user against Active Directory. I chose System.DirectoryServices.AccountManagement (available for .NET Core).
Now, I had to create a new controller with an [AllowAnonymous]attribute. I called it LoginController, and created an action that looked like the following:
[AllowAnonymous]
[HttpPost]
// Notice: We get a custom request object from the body
public async Task<IActionResult> Login([FromBody] AuthRequest request)
{
// Create a context that will allow you to connect to your Domain Controller
using (var adContext = new PrincipalContext(ContextType.Domain, "mydomain.com"))
{
var result = adContext.ValidateCredentials(request.username, request.password);
if (result)
{
// Create a list of claims that we will add to the token.
// This is how you can control authorization.
var claims = new[]
{
// Get the user's Name (this can be whatever claims you wish)
new Claim(ClaimTypes.Name, request.username)
};
// Read our custom key string into a a usable key object
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("SOME_TOKEN").Value));
// create some signing credentials using out key
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// create a JWT
var token = new JwtSecurityToken(
issuer: "mydomain.com",
audience: "mydomain.com",
claims: claims, // the claims listed above
expires: DateTime.Now.AddMinutes(30), // how long you wish the token to be active for
signingCredentials: creds);
Since we return an IActionResult, wrap the token inside of a status code 200 (OK)
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
}
}
}
// if we haven't returned by now, something went wrong and the user is not authorized
return Unauthorized();
}
The AuthRequest object could look something like this:
public class AuthRequest
{
public string username { get; set; }
public string password { get; set; }
}
Now, in my React app, all I have to do is make a simple fetch request to the LoginController with the user's username & password that I can get from a login form. The result will be a JWT I can save to state (But should save to cookies: the react-cookie library makes that trivial).
fetch(`login`, {
method: "POST",
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify({this.state.username, this.state.password})
}).then((response) => {
if (response.status === 401) {
// handle the 401 gracefully if this user is not authorized
}
else {
// we got a 200 and a valid token
response.json().then(({ token }) => {
// handle saving the token to state/a cookie
})
}
})
You now have the ability to add the [Authorize] attribute to any of your controllers in your .NET Core application, and make a fetch request to it while passing your JWT from your React client, like this:
await fetch(`someController/someAction`,
{
method: 'GET'
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${YOUR_JWT}`
}
})
.then(response => doSomething());
If you wanted to use this JWT with a SignalR Hub, add the [Authorize] attribute to your Hub in your .NET Core project. Then, In your React client, when you instantiate the connection to your hub:
import * as signalR from '#aspnet/signalr';
var connection = new signalR.HubConnectionBuilder().withUrl('myHub', { accessTokenFactory: () => YOUR_JWT })
And, viola! A .NET Core React application capable of authorized real-time communication!
I am trying to set up a minimal layer of authentication between my Rails backend and my React front end, but I am running into some problems.
I cannot seem to find the cookie key value that the server passes down to my client. In the network tab, I see it in the response: Set-Cookie:_skillcoop_session=...., but when I use js-cookie to look for the above cookie, _skillcoop_session, I only see one called identity-token=... and its value is different from _skillcoop_session. How do I access _skillcoop_session in the browser?
What header key do I pass up to the server to signal to my backend to use 'this' header key to match up with the session it has stored off? In this post, Justin Weiss seems to suggest that I make the request to the server with a header like: Cookie: _skillcoop_session=....
Am I doing this all wrong? Would I be better off using a gem like devise?
Also in order to load the session in my other controllers, I have had to do something like session['init'] = true, and I learned to do this from this SO post. This seems hacky. Why do I have to manually reload the session in separate controller actions after I've set it previously in a different controller action in a different request?
I'm currently just stubbing out the user and the authentication -- all I want to do to get the plumping in place is set a session[:user_id] and be able to read that session data in other controller actions. For this I have two main files for consideration: UsersController and Transport.js. In UsersController I am just stubbing the session[:user_id] with the number 1 and in Transport.js I'd like to pass the cookie received from the server so that the backend can maintain a session between requests with a client.
Here is my controller:
class UsersController < ApplicationController
def create
session[:user_id] = 1
render json: user_stub, status: :ok
end
def show
puts "user id: #{session[:user_id]}"
# should return, 1, but is returning, nil...why?
render json: user_stub, status: :ok
end
private
def user_stub
{
id: 1,
email: params['email'] || 'fakeemail#gmail.com',
password: params['password'] || 'fake password'
}
end
end
Here is the main location of my app where I make my request to the server - it's in an abstraction I call Transport.js:
require('es6-promise').polyfill();
require('isomorphic-fetch');
var cookie = require('js-cookie');
const GET = 'GET';
const POST = 'POST';
function Transport() {
}
Transport.prototype.get = function(url, options = {}) {
return this.query(GET, url, null, options);
};
Transport.prototype.post = function(url, dataString, options = {}) {
return this.query(POST, url, dataString, options);
};
Transport.prototype.query = function(method, url, dataString, options = {}) {
var data;
if (dataString) {
data = JSON.parse(dataString);
}
switch(method) {
case GET:
return fetch(url, Object.assign({headers: {'Cookie': cookie.get('_skillcoop_session')}}, options, {
method: method
}));
case POST:
return fetch(url, Object.assign({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}, options, {
method: method
}));
default:
throw new Error("This HTTP Method is not supported.");
}
};
module.exports = Transport;
According to this SO post, one cannot access the Set-Cookie header in JS. Thus, I suppose my attempts to handle Set-Cookie in the response headers was a fools effort.
According to the NPM package that I'm using to make HTTP requests, I need to pass {credentials: 'same-origin'} key value pair in the second argument to fetch, which will 'automatically send cookies for the current domain'. That did the trick -- the session object is available and contains the user_id that was set in the session in the previous request in a different action.
Yes. I changed up how I approached this problem. I leaned very heavily on this Reddit post. In short, I use ruby-jwt on the backend and store the token in localStorage on the front end. Each request out to the server will include the token in a header AUTHORIZATION.
In following steps 1 and 2, it looks like I no longer have to 'reload the session'.
I have a requirement of storing andd displaying the profile picture of my users. So I used the S3.upload function in my nodeJs backend to store the image.
And after the image is stored I stored the link in database to fetch it using ng-source in my view. It worked but the link got expired after few hours and did not work. Below is the code for my upload. Is there any solution in how to do this or any other better way to do this.
var body = fs.createReadStream(file.file.path);
//Upload the photo to AWS
s3.upload({Body: body}, function (err, data) {
if (err) {
res.sendStatus(500);
}
if (data) {
//getSignedUrl and Store it in Database
var AWS = require('aws-sdk');
var url = req.params.doctorId + "/Images/" + fileName;
var s3 = new AWS.S3()
,params = {Bucket: S3Bucket, Key:url };
s3.getSignedUrl('getObject', params, function (err, url) {
if (err || url == null) res.status(500).send({msg: "amazon s3 error"});
else if (url) {
if(req.body.picture == 1) {
User.findByIdAndUpdate(req.params.doctorId, {$set: {'FileName.profilePicture': url}},
function (err, doc) {
if (err)
res.sendStatus(500);
else
res.send({url: url});
});
This is because you're getting the URL from a signed URL and signed URLs expire by design.
From Share an Object with Others on AWS docs:
All objects by default are private. Only the object owner has permission to access these objects. However, the object owner can optionally share objects with others by creating a pre-signed URL, using their own security credentials, to grant time-limited permission to download the objects.
It seems like you're not exactly storing "secret" resources here that access has to be granted to, then the best approach here is store the image publicly. This is trivial to do and you simply have to set the ACL to public-read when you call PutObject or upload. That way you'll know the URL for the object without actually having to retrieve it:
https://s3-[region].amazonaws.com/[bucket]/[file]
This is what your upload statement would look like then:
s3.upload({ Body: body, ACL: 'public-read' }, function (err, data) {
I'm very new to the MEAN stack, and this might seem to be very naive or wrong approach, but I want to ask that when we authenticate using passport-facebook strategy, using the following code:
var FacebookStrategy = require('passport-facebook').Strategy;
var User = require('../models/user');
var fbConfig = require('../fb.js');
module.exports = function(passport) {
passport.use('facebook', new FacebookStrategy({
clientID : fbConfig.appID,
clientSecret : fbConfig.appSecret,
callbackURL : fbConfig.callbackUrl
},
// facebook will send back the tokens and profile
function(access_token, refresh_token, profile, done) {
console.log('profile', profile);
// asynchronous
process.nextTick(function() {
// find the user in the database based on their facebook id
User.findOne({ 'id' : profile.id }, function(err, user) {
// if there is an error, stop everything and return that
// ie an error connecting to the database
if (err)
return done(err);
// if the user is found, then log them in
if (user) {
return done(null, user); // user found, return that user
} else {
// if there is no user found with that facebook id, create them
var newUser = new User();
// set all of the facebook information in our user model
newUser.fb.id = profile.id; // set the users facebook id
newUser.fb.access_token = access_token; // we will save the token that facebook provides to the user
newUser.fb.firstName = profile.name.givenName;
newUser.fb.lastName = profile.name.familyName; // look at the passport user profile to see how names are returned
//newUser.fb.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
// save our user to the database
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
return done(null, newUser);
});
}
});
});
}));
};
I don't need to store the user information in any data store. I want to store the token only for the time the user is logged into my web application, basically I don't have the need to use Mongo, because all the data that will be displayed in the web application will come from Facebook api, for example the posts for a profile, the number of likes on a particular posts etc. I don't need to have a backend as such, because if I store the data in any data store such as Mongo, the next time the user login then the data will be stale (in a way the Facebook api is kind of my backend), and I also want that the updates for information on any posts done on Facebook should be updated realtime on my web application for e.g. if someone likes a post on the actual Facebook page the number of likes on my web application should also be updated in realtime, so it seems unnecessary to first bring the data from the Facebook SDK and then store it in Mongo, why not just give it to the controller and from there the view can present the data. If my approach is wrong please do correct me.
So basically every time the user logs in an access token is created and used for that session, when the user logs out the access token is destroyed and so completely eliminates the need for storing the token and any data that is brought in using the Facebook SDK.
Replace the function call
User.findOne({ 'id' : profile.id }, function(err, user) {
With facebook sdk authentication call and return the user object when it's validated.
return done(null, user);
Please refer...
https://github.com/jaredhanson/passport-facebook
you need to create a new user template in the model folder. I have created the following: user.js
var facebook = module.exports.facebook = {
id : String,
token : String,
email : String,
name : String
}
and then change the passport.serializeUser and passport.deserializeUser functions.
passport.serializeUser(function(user, done) {
done(null, user.facebook.id);
});
// used to deserialize the user
//passport.deserializeUser(function(id, done) {
passport.deserializeUser(function(id, done) {
done(null, { id: User.facebook.id, token: User.facebook.token, name: User.facebook.name, email: User.facebook.email})
});
then the function: process.nextTick(function() {} replace the content by this code :
var newUser = User;
// set all of the facebook information in our user model
newUser.facebook.id = profile.id; // set the users facebook id
newUser.facebook.token = token; // we will save the token that facebook provides to the user
newUser.facebook.name = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned
newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
return done(null, newUser);
add the line profileFields: ['id', 'displayName', 'photos', 'emails', 'name'] in function passport.use(new FacebookStrategy({}
change the profile.ejs file by removing the local information div and changing the properties <% = user.facebook.id%> to <% = user.id%> and so on in the others.