We have an Outlook Add-In which runs in OWA.
The Manifest sits on https://company.ourdomain.com
The Javascript sits on https://company.ourdomain.com
The Custom Web Service we wrote in-house sits on https://company.ourdomain.com
When I make a call from within JavaScript in response to an Add-In Command, I use the format https://company.ourdomain.com/api/Controller/Action in the ajax call.
I end up getting one of those CORS errors (sometimes it's pre-flight, other times CORB). Why am I getting this if the Javascript is literally sitting on the same domain as the web service?
I'm assuming I'm authenticated since I've logged into my Outlook account.
What gives?
NOTE:
As an experiment I attempted a RESTful call by directly typing in the URL (No OWA involved). This caused the code to Authenticate against Azure AD. Then afterward I logged into OWA in the same browser session and everything worked fine. Do I actually need to authenticate within the Javascript even if the webservice I'm calling is in the same domain?
AJAX CALL WHICH GENERATES ERROR
Remember, it will work just fine after I've made a RESTful call by making a call to my web service directly from the Browser
var apiUri = '/api/People/ShowRecord';
$.ajax({
url: apiUri,
type: 'POST',
data: JSON.stringify(serviceRequest),
contentType: 'application/json; charset=utf-8',
dataType: 'json'
}).done(function (response) {
if (!response.isError) {
// response to successful call
}
else {
// ...
}
}).fail(function (status) {
// some other response
}).always(function () {
console.log("Completed");
});
OBSERVATION
When I call the api from the Address Bar the code below is run. This code never gets invoked by Javascript
[assembly: OwinStartup(typeof(EEWService.AuthStartup))]
namespace EEWService
{
public partial class AuthStartup
{
public void Configuration(IAppBuilder app)
{ app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "ourdomain.com";
return Task.FromResult(0);
}
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new string[] { $"spn:{ConfigurationManager.AppSettings["ida:Audience"]}" }
}
});
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
});
}
}
}
There are a few problems with this I think.
The first one is you are trying to serve your static content off the same server you are serving your code from. This is in general considered a bad-practice, purely because no point in wasting those precious server resources for static content. Ideally you should upload your static content to a CDN - and let the users' browser make a request to some super-cached file server. However - I understand this option might not be available to you as of now. This also isn't the root cause.
The second and the real problem is, (you think you are but) you are not authenticated. Authentication in Outlook web-addins doesn't come by default, it's something you need to handle. When Outlook loads your web add-in into the side panel it makes certain methods available to you which you can use and kind-of create a pseudo-identity (as an example Office.context.mailbox.userProfile.emailAddress ) - but if you want real authentication, you will need to do that yourself.
There are three ways of doing that as far as I can tell.
The first one is through the Exchange Identity Token
Second one is through the Single Sign On feature
The third one - which I think is the most convenient and the simplest in logic to implement is using WebSockets. (SignalR might be what you need).
When the user loads your first page, make sure a JS value like window.Unique_ID available to them. This will come in handy.
Have a button in your UI - which reads "Authenticate"
When the user clicks to this button, you pop them out to a url which will redirect to your authentication URL. (Something like https://company.ourdomain.com/redirToAuth). This would save you the trouble of getting blocked in the side-panel, because you are using window.open with a url that's on your domain. Pass that Unique_ID to redirection which then redirects you to OAuth login URL. That should look like https://login.microsoftonline.com/......&state=Unique_ID
Right after popping the user to sign in window, in your main JS (which is client-side), you open a web-socket to your server, again with that Unique_ID and start listening.
When the user completes authentication, the OAuth flow should post back either an access token, or the code. If you get the access token, you can send it through the sockets to front-end (using the Unique_ID which is in the parameters of post-back) or if you had the code, you finish authenticating the user with a server-to-server call and pass the access token the same way afterwards. So you use that unique Id to track the socket that user connected from and relay access token to only that user.
Related
I am trying to get a very simple Unity application (Unity Version 2019.4.20f1 Personal) to authenticate with Facebook's SDK for unity. I have set up a local HTTPS server (with self-signed certificates) using Django & Gunicorn (Yes, not ideal, but just trying this out first), which serves the build products of Unity's WebGL.
Here is my one and only script in unity:
public class FacebookHelper : MonoBehaviour
{
// Awake function from Unity's MonoBehavior
void Awake ()
{
if (!FB.IsInitialized) {
// Initialize the Facebook SDK
FB.Init(InitCallback, OnHideUnity);
} else {
// Already initialized, signal an app activation App Event
FB.ActivateApp();
}
}
private void InitCallback ()
{
if (FB.IsInitialized) {
// Signal an app activation App Event
FB.ActivateApp();
// Continue with Facebook SDK
// ...
} else {
Debug.Log("Failed to Initialize the Facebook SDK");
}
}
private void OnHideUnity (bool isGameShown)
{
if (!isGameShown) {
// Pause the game - we will need to hide
Time.timeScale = 0;
} else {
// Resume the game - we're getting focus again
Time.timeScale = 1;
}
}
public void Login() {
var perms = new List<string>(){"public_profile", "email"};
FB.LogInWithReadPermissions(perms, this.AuthCallback);
}
public void Logout() {
FB.LogOut();
Debug.Log("User logged out.");
}
public void AuthCallback (ILoginResult result) {
if (FB.IsLoggedIn) {
// AccessToken class will have session details
var aToken = Facebook.Unity.AccessToken.CurrentAccessToken;
// Print current access token's User ID
Debug.Log("UserID: " + aToken.UserId);
// Print current access token's granted permissions
foreach (string perm in aToken.Permissions) {
Debug.Log("Permission: " + perm);
}
} else {
Debug.Log("User cancelled login");
}
}
}
where the Login() and Logout functions are called respectively when the two buttons below are pressed:
In Chrome, I have two issues:
I am getting CORS and CORB errors, notably only on the Logout() action, and not on the Login action.
For the Login() action, the Login Dialog is problematic because it opens a new tab as per usual, but it never allows me to input my login details before immediately closing this new tab. For some reason, my logging statements still say that FacebookSDK has successfully logged in.
In an attempt to solve this, I have
Enabled CORS on my Django application, according to instructions here. Sadly, this didn't work.
On the Facebook platform, I have done all these, to no avail as well:
Added localhost to the App Domain
Enabled Allow Cross Domain Share Redirects
Added https://localhost:8080 as the Website
Added https://localhost:8080 as a valid OAuth redirect uri to the 'Facebook Login for Gaming' Product
However, in Safari, I met with none of the aforementioned problems. The login flow works fine.
EDIT:
However, something I encountered on both Chrome and Safari is that for some reason, when the SDK is initialized, it always automatically signs in a default user. For now, I will just add some code to sign out at initialize time to remedy this.
As usual, spent quite a while on this but wasn't able to figure out how I can solve this. According to my understanding, I have to make sure the responses to my server have the Access-Control-Allow-Origin header, and these may be some ways I think I can do it:
Somehow make FacebookSDK set the Access-Control-Allow-Origin header on the response? Maybe their GraphQL API will be more helpful?
Use Nginx to reverse proxy and serve the static files instead, and then I should be able to do something like adding the Access-Control-Allow-Origin header as well. I have thought of doing this but it'll take a bit of investigation, so I haven't tried this yet.
For the CORB issues, I don't really have any ideas though.
I really appreciate it if anyone has taken the time to read up till here, but anyways, thank you to anyone who can help at all! :)
EDIT 1:
Here is the request/response for Safari:
And here are the request/response for Chrome:
The observations I can make is that for some reason Facebook is not returning the Access-Control-Allow-Origin in the response for the Chrome request. Maybe it is due to the additional headers in the Chrome request?
You need to use a third library or do that in Chrome itself but it's prerence dependent
pip install django-cors-headers
So in Settings:
INSTALLED_APPS = (
...
'corsheaders',
...
)
MIDDLEWARE_CLASSES = (
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
Or check out his docs for more details https://github.com/adamchainz/django-cors-headers#setup
You know, web applications needs sessions or cookies to authentication. I trying to build web application with Vue.JS and Flask microframework for example ERP or CRM.
I'm confused. How can I work with sessions? Let's think we have a code like this in the Flask:
import os
from flask import Flask, request, jsonify, abort, session
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') or \
'e5ac358c-f0bf-11e5-9e39-d3b532c10a28'
#app.route('/login', methods=['POST'])
def user_login():
user = request.form['user']
session['isLogged'] = True
return jsonify({'status': session['isLogged']})
#app.route('/user-info')
def user_info():
if 'isLogged' in session:
return jsonify({'user': 'ali'})
else:
return jsonify({'error': 'Authentication error'})
and our front-end codes should be like this:
mounted() {
this.checkIsLogged();
},
methods: {
checkIsLogged() {
fetch('http://127.0.0.1:5000/user-info', {
mode: 'no-cors',
method: 'GET',
}).then((resp) => {
return resp;
}).then((obj) => {
if(obj.user) {
this.status = true
}
})
},
login() {
let frmData = new FormData(document.querySelector("#frmLogin"));
fetch('http://127.0.0.1:5000/login', {
mode: 'no-cors',
method: 'POST',
body: frmData,
}).then((resp) => {
return resp;
}).then((obj) => {
this.status = obj.status
})
}
}
Everything is normal until I refresh the page. When I refresh the page, I lose the sessions.
Server-side sessions are important for many reasons. If I use localStore or something like that how could be secure I have no idea.
I need some help who worked on similar projects. You can give me suggestions. Because I never worked similar projects.
Other stuff I've read on this topic:
Single page application with HttpOnly cookie-based authentication and session management
SPA best practices for authentication and session management
I'm still confused to about what can I do.
Session handling is something your SPA doesn't really care much about. The session is between the user-agent (browser) and the server. Your vue application doesn't have much to do with it. That's not to say you can't do something wrong, but usually the issue is not with your front end.
That being said it's tough do give an answer to this question because we don't really know what's wrong. What I can do is give you instructions on how you can diagnose this kind of problem. During this diagnosis you'll figure out where the actual issue is and, at least for me, it usually becomes obvious what I need to do.
Step 1)
Use some low level HTTP tool to check the Server response (personally I use curl or Postman when lazy). Send the login request to the server and take a look at the response headers.
When the login is successful you should have a header "Set-Cookie", usually with a content of a "sessionid" or whatever key you're using for sessions.
If you don't see a "Set-Cookie" one of the following is true:
Your server did not start a session and thus did not send a session cookie to the client
there's a proxy/firewall/anti-ad- or tracking plugin somewhere filtering out Cookies
If you see the Set-Cookie Header continue with Step 2, otherwise check the manual in regards to sessions in your chosen backend technology.
Step 2)
Thankfully most modern browsers have a developer console which allows you to do two things:
1) Check your HTTP request headers, body and response headers and body
2) Take a look at stored cookies
Using the first feature (in Chrome this would be under the "Network" tab in the developer console) diagnose the request and response. To do so you need to have the developer console open while performing the login in your app. Check the response of the login, it should contain the Set-Cookie if the login was successful.
If the cookie is not present your server doesn't send it, probably for security reasons (cross-origin policies).
If it is present, the cookie must now be present in the cookie store. In chrome developer console, go to the "Application" tab, expand Cookies from the left menu and take a look at the hosts for which cookies are present. There should be a cookie present which was set in the step before. If not the browser didn't accept the cookie. This usually happens when your cookie is set for a certain domain or path, which isn't the correct one. In such a case you can try to set the domain and/or path to an empty or the correct value (in case of the path a "/").
If your cookie is present, go to step 3
Step 3)
Remember when I said the app has nothing to do with the session. Every request you send either with ajax or simply entering a valid URL in the browser sends all cookies present for this host in the request headers. That is unless you actively prevent whatever library you're using to do so.
If your request doesn't contain the session cookie one of the following is usually true:
the usage of your http library actively prevents sending of cookies
you're sending a correct request but the cookie-domain/path doesn't match the request host/path and is thus not sent along
your cookie is super shortlived and has already expired
If your cookie is sent correctly then your sessions handling should work unless your server doesn't remember that session or starts a new session regardless of an existing session.
I realise this question is quite old and this extensive answer comes way too late, however someone with similar problems may be able to profit from it.
I keep getting this error message while trying to implement the Meteor.loginWithFacebook() method. Login used to work for my app now it doesn't anymore and I have no idea why:
I have it set so when the facebook login button is clicked, the Meteor.loginWithFacebook() method is called, like so (client-side):
'click .facebook-login':function(event){
Meteor.loginWithFacebook({requestPermissions:['user_photos', 'user_videos'], loginStyle:"popup"},function(err){
if (err)
throw new Meteor.Error(err, "Facebook Login Failed");
});
}
Server-side:
ServiceConfiguration.configurations.upsert(
{ service: "facebook" },
{
$set:{
// Development
appId: "App-ID-String",
secret: "App-Secret-String"
}
}
);
Oddly enough, this works fine when working in development but when I push my code to my production server, I get the error above. This code also worked for several months and then just stopped working, in production. I've cleared both databases to start from scratch and I still get the error in production but not development.
Before you mention it, I have separate code blocks to handle the different appId and secret based on development versus production, but the code is essentially the same and accounts for both environments.
Turns out that the Facebook SDK is particular about your URL prefix/host (if there is or is not a 'www' in front of your URL) and treats the URLs as separate sites. When going to my site to login, the non-www prefixed URL version of my site could not login to Facebook but the 'www' one could.
I was able to fix this by forcing my app to reload the 'www' prefixed URL if the non-www URL was accessed using the following code. I tried forwarding using the GoDaddy DNS interface but it didn't work.
Meteor.startup(function () {
if (location.host.indexOf('www') === -1) {
location = 'http://www.example.com';
}
});
This instantly fixed my primary issue though it seems this isn't the most ideal way of handling this since it basically loads the application twice if the user visits the non-www URL.
I need associate logged users in my django web app with their socket.io sockets in node.js app, because for real-time part of web i want to use node.js. Session data will be stored in database. I think i can access cookies in browser, so i can send to node.js app cookie value, that acts like user identifier, but i think this isn't good idea. Is there any other way how to do this?
The cookie can be accessed inside the authorization event. So if you're already using a cookie to store session authentication data, it's pretty easy to integrate that into socket.io. There are some pre-built modules for doing this sort of thing, like SessionSockets.
This article gives some insights into how to do it yourself. The most important section is exerpted below:
var parseCookie = require('connect').utils.parseCookie;
sio.set('authorization', function (data, accept) {
// check if there's a cookie header
if (data.headers.cookie) {
// if there is, parse the cookie
data.cookie = parseCookie(data.headers.cookie);
// note that you will need to use the same key to grad the
// session id, as you specified in the Express setup.
data.sessionID = data.cookie['express.sid'];
} else {
// if there isn't, turn down the connection with a message
// and leave the function.
return accept('No cookie transmitted.', false);
}
// accept the incoming connection
accept(null, true);
});
sio.sockets.on('connection', function (socket) {
console.log('A socket with sessionID ' + socket.handshake.sessionID
+ ' connected!');
});
I have the following NancyFX unit test.
var browser = new Browser(new UnitTestBootstrapper());
var response = browser.Post("/login", with =>
{
with.FormValue("UserName", userName);
with.FormValue("Password", password);
});
response.ShouldHaveRedirectedTo("/home");
You can see that I use an instance of Nancy.Testing.Browser to POST some form values. I would like to capture this Http request in Fiddler but I am not sure how to set-up the Browser (a proxy perhaps?)
Thanks
You can't because they never hit the network; that's the whole point of the browser class - to give you end to end testing without the performance hit/configuration issues of having to use hosting/http/networking/browser rendering.
If you want to go via the networking stack then use something like Selenium, or spin up a self host and poke it with EasyHttp or manually with HttpClient.