Using SSO with Office.js Excel add-in - how to set the audience for the token? - single-sign-on

TL;DR - Is there a way to get a bearer token with a custom audience (and perhaps authority) from the new SSO stuff in an Office.js?
Details -
We're trying to use the new SSO stuff for an Office.js add-in, but we're running into the problem that the bearer token from OfficeRuntime.auth sets the audience to the GUID for our add-in; we want to set a different audience (our API application) like we used to with MSAL, but there doesn't seem to be any option for that. We're using getAccessToken from OfficeRuntime.auth:
const token = await OfficeRuntime.auth.getAccessToken({
allowSignInPrompt: withUI,
allowConsentPrompt: withUI
});
(withUI is just a true/false flag we send the function this is in depending on whether showing a UI is okay.)
The options documentation doesn't show an option for "scope" or "audience" (or "authority") that we can see. Granted this stuff is all preview, so it could be a matter of "it's just not in there yet."
Our setup is:
+−−−−−−−−−−−−−−−−−+
| Browser / Excel |
+−−−−−−−−−−−−−−−−−+
| |
| +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+
| | Our add−in |<−−−−−>| Server w/ our API |<−−−−−>| Microsoft Graph |
| +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+
| | ^
+−−−−−−−−−−−−−−−−−+ |
v
+−−−−−−−−−−−−−−−−−+
| Azure DB |
+−−−−−−−−−−−−−−−−−+
Our add-in is set up as an Azure application and, separately, our API server is set up as an Azure application. The add-in has access rights to the API; the API has access rights to the various things it needs to satisfy API calls. That way, the relationships between the pieces are visible in the Azure configuration.
When we found we couldn't use the token from SSO, we looked at its contents and that's when we realized the audience wasn't what we wanted. For now we're working around it by adding the add-in's audience and authority to the options in the API project's authentication step:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
Configuration.Bind("AzureJwt", options);
// These next two are the ones we had to add to make it work
// when we realized there was an audience problem
options.Audience = "guid-for-the-add-in";
options.Authority = "https://login.microsoftonline.com/relevant-guid-here/v2.0";
});
But that feels like a workaround rather than a solution; our Azure config doesn't represent what's going on, we have hardcoded authentication instead.
Apologies if some of my terminology is off. Azure is new for me (though not for another member of the team).

This behavior is by design. For Office SSO, the web API has to be the same domain and AAD GUID as the add-in.
If you want to keep them separate, then the workaround you've found seems pretty good. (I'm surprised that you found a way.)
<highly speculative>
You might also want to try putting the add-in's GUID in the Authorized Client Applications section of the API's AAD registration.
</highly speculative>

It turned out to be a really silly mistake: We were bothered by having to specify the audience and authority in the API project (in the code in the question), but it turns out we were already specifying them in the API project — they were defined (with different values) in appsettings.json. So the code we added just overwrote them with the correct values.
Changing appsettings.json's Audience and Authority settings, and removing the code overriding them, worked just fine. So it was already a bit hardcoded (or at least not controlled via AD), just in a config file rather than code.

Related

OneNote on SharePoint REST API

The OneNote team recently published a blog post in which they explain how to access data from OneNote notebooks that are stored on SharePoint ('site-hosted notebooks', as they refer to them).
I'm trying to use the API as they describe it, but I'm unable to get it to work. Specifically, the first step is to find out my site collections and site ID using the following API call:
https://www.onenote.com/api/beta/myorganization/siteCollections/FromUrl(url='{full-path-to-SharePoint-site}')
It seems to fail no matter what I do: I tried to put the full path to my SharePoint site with and without 'http://' or 'https://', tried to write my organization name in various ways (with '-my' at the end, and without it), and probably a few other combinations as well - it just doesn't work. I keep getting error 400 / bad request.
Any help would be greatly appreciated.
So ... I had a fairly long correspondence with the guys from Microsoft, and here's what I learned:
If you're using a browser or a browser plug-in to test some of your REST APIs calls, it's not enough to be logged-in as the relevant user. This works for SharePoint and other Office 365 APIs, but not for this OneNote API. You must send "Authorization: Bearer ... " in the request headers.
Probably my stupid mistake, but 'myorganization' in the URL https://www.onenote.com/api/beta/myorganization/siteCollections/FromUrl(url='{full-path-to-SharePoint-site}') is not the name of your organization / domain: it's the literal string 'myorganization'.
Finally, the 'full-path-to-sharepoint-site' should point to https://xyz.sharepoint.com and not to https://xyz-my.sharepoint.com.
This syntax works like a charm for me:
https://www.onenote.com/api/beta/myorganization/siteCollections/FromUrl(url='https://XYZ.sharepoint.com/sites/ala')

How to use new enhanced sessions in Parse with users created on cloud code?

I was trying out the new enhanced revocable sessions in Parse on my Android app. It works well when logging in or signing up via email password or facebook but doesn't work well for custom authentication, e.g. google+.
I'm currently logging in the user using the cloud code which also creates the new user when signing up. This does not create a new Session object, that means the new enhanced sessions are not used and it still uses the legacy sessions.
I pass the session token back to client where using the become method the user logs in but it's the legacy sessions.
This feels like the feature is not complete but I would really like to move to the new enhanced sessions with my app. Has anyone worked with them yet? Are there any workarounds using the REST API or by creating the sessions manually and handling them manually? I looked into the JS API but it says it's only read only.
Here's the Blog post on Enhanced Sessions.
Where should I go next?
Yes, I found a solution but it's a workaround, works for my case because I don't support signing up with user/password.
Basically, the solution (cloud code) in semi pseudo-code is:
Fetch the user with master key
Check if user.getSessionToken() has value
if it has, return the session token and do a user.become() in the client as usual
if it's not, here the workaround, do the following:
yourPreviousPromiseInOrderToChainThem.then(function(user)
password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
});
password = password.toString('base64')
user.setPassword(password);
return user.save();
}).then(function(user) {
return Parse.User.logIn(user.get('username'), password)
}).then(function(user) {
var sessionToken = user.getSessionToken();
// Return the session token to the client as you've been doing with legacy sessions
})
That means, I'm changing the user password each time in order to make a remote login and, of course, I know thist can't be applied to all cases, it's enough for app because I don't support login with user/password (only third party logins) but I understand that maybe it's not for all cases.
I got the idea from this official Parse example.
I don't like this solution because I think is not a workaround, it's a mega hack but I think there is no other way to do it currently (either Parse.com or Parse-Server)
If you find other workaround, please, share it :)

Authentication not working with VersionOne REST API

Ok, I have a technical question here. We've developed an integration component in XStudio so that we can pick VersionOne's "Stories" (as "Requirements" in XStudio) and "Defects" (as "Bugs" in XStudio). This way you can execute your tests and manage the results, metrics etc. from XStudio but also manage the complete traceability matrix (Products -> Requirements -> Tests -> Test campaigns -> Bugs) in XStudio using VersionOne's items. We handle the links our side.
To do this, we implemented the connector using VersionOne's REST API.
Everything works great! very fast etc.
We tested it using a free server from VersionOne with no problem. Our Java code manage cookies so that it authenticates using "Basic Authentication" protocol, we retrieve the cookie from VersionOne, store it in the local CookieStore and provide that cookie in the next requests so that we do not have to authenticate again and again. All this worked fine from our side.
The content looks like this:
{X-Instart-Request-ID=[7405870175418545839:SEN01-NPPRY09:1396448658:44],
null=[HTTP/1.1 200 OK],
Date=[Wed, 02 Apr 2014 14:24:17 GMT],
Content-Length=[16063],
Expires=[-1],
VersionOne=[Ultimate/14.0.7.6706; Scrum],
Set-Cookie=[.V1.Ticket.ncnuaaa=HFZlcnNpb25PbmUuV2ViLkF1dGhlbnRpY2F0b3LqgwAAB1hTdHVkaW+CjqLWdBzRCP8/N/R1KMorEByFu31RuGY+eqVCi1FHvTE=; path=/; HttpOnly],
Connection=[keep-alive],
Content-Type=[text/xml; charset=utf-8],
Server=[Microsoft-IIS/8.0],
Pragma=[no-cache],
Cache-Control=[no-cache]}
BUT... when we run our code on our client's environment, we don't get the original cookie for any reason !?
{cache-control=[no-cache],
content-type=[text/xml; charset=utf-8],
null=[HTTP/1.1 200 OK], expires=[-1],
content-length=[16063],
server=[Microsoft-IIS/8.0],
date=[Wed, 02 Apr 2014 12:34:08 GMT],
pragma=[no-cache]}
When our code get the header fields from the connection and we try to get the "Set-Cookie" field it can't find it and a popup is automatically display.
Map<String, List<String>> headerFields = connection.getHeaderFields();
List<String> cookiesHeader = headerFields.get("Set-Cookie");
The popup is asking to authenticate (by the way on "www6.v1host.com/192.33.31.50" while it was more expected "www6.v1host.com/abcded" - maybe ther's a clue here?).
If we authenticate on your server here everything continues normally and everything works ok.
But we shouldn't have to authenticate again as we do it in the connection before:
String plainAuth = username + ":" + password;
encodedAuth = ("Basic " + new String(Base64.encode(plainAuth.getBytes()))).replaceAll("\n", "");
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", encodedAuth);
connection.setRequestProperty("Connection", "keep-alive");
So, not sure if this is because the authentication is not working (it would explain why the cookie is not returned and the popup ask the user to explicitly authenticate) or there is something specific in terms of cookie management...
Do you have any idea what could be going on here?
This code is working well on many different other REST APIs using Basic Auth. and Cookies.
Thanks in advance,
It sounds like you work for XQual, the developers of XStudio. If so, please reach out to me. We are always happy to list another integration.
Assuming this is intended to work for more than just 1 customer, I have a couple pieces of advice:
Provide a meaningful user agent header. This helps us be pro-active with your integration. The header is useful even for custom, one-off products, but is even more important when other vendors are involved.
Use OAuth2 for authentication. There are plenty of good libraries for OAuth2 in Java. We have an upcoming blog post where we show how it can be done with Apache Oltu.
To your specific question, I have some hunches:
You might be expecting too much about how VersionOne is deployed. VersionOne is offered both on-premise and on-demand. Your customer may be putting "192.33.31.50" into the configuration to represent on-premise, while you are expecting an instance name for on-demand. Also beware that not all on-demand instances are on "www6".
If on-premise, VersionOne also offers an installation option for Windows Integrated Authentication. In which case, you may not be getting the headers you were expecting. This is one reason I recommended OAuth2 above. OAuth2 is always available for API calls, regardless of the user authentication mechanism.
To better diagnose, could you share some code that shows how you construct the request URL?

GitHub OAuth in lua

I am working on a library in LUA for an ipad app called Codea. I'm trying to figure out to use OAuth for GitHub Gists. Only part that i can not figure out is how to get an Auth token via code. I used curl in terminal to get myself a token but this seems to be to much work for other users.
I've read through the github api docs multiple times but I cant figure out how to get a Token programmatically. I've tried to duplicate the method I've used to GET and POST gists but it does not seem to work. I'm not sure how to pass the username and password.
I'm creating a table with the needed params then encoding it in json. Everything I try gets a 404 error or 500 error. Thank you all in advance.
local url = "https://api.github.com/authorizations"
local d = {}
d.scopes = {"gist"}
d.note = "AutoGist Codea"
projectAuth = json.encode(d)
opts = { data = projectAuth }
opts.method = "POST"
opts.headers = {Authorization = "basic " .."username:password"}
http.request(url,successCallback,failedCallback,opts)
Scopes are coming, but only in Q4 2013.
See "OAuth changes coming" (October 2013, by Tim Cleam - tclem):
Starting today, we are returning granted scopes as part of the access_token response.
For example, if you are making a POST with the application/json mime-type you’ll see an additional field for the granted scopes.
{
"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a",
"scope":"repo,gist",
"token_type":"bearer"
}
Right now, these scopes will be identical to what you requested, but we are moving towards a feature set that will allow GitHub users to edit their scopes, effectively granting your application less access than you originally requested.
You should be aware of this possibility and adjust your application behavior accordingly.
Some things to watch out for and keep in mind:
Most third party applications using GitHub OAuth to identify users have the best success in adoption by starting out with a request for the minimum access that the application can possibly get away with.
Something like no scopes or just user:email is very sane.
It is important to handle the error cases where a users chooses to grant you less access than you originally requested.
Now that we are surfacing the granted scopes on the access_token response, applications can warn or otherwise communicate with their users that they will see reduced functionality or be unable to perform some actions.
Applications can always send users back through the flow again to get additional permission, but don’t forget that users can always say no.

Google Data/OAuth/AppEngine/Python - Properly Registering a Web Application

I'm creating a webapp with this combination of tools. I'm authenticating with App Engine in the following manner:
class googleLogin(webapp.RequestHandler):
def get(self):
callbackURL = 'http://%s/googleLoginCallback' % getHost()
#Create a client service
gdClient = gdata.docs.service.DocsService()
gdata.alt.appengine.run_on_appengine(gdClient)
gdClient.SetOAuthInputParameters(gdata.auth.OAuthSignatureMethod.HMAC_SHA1,
_GoogleConsumerKey,
consumer_secret=_GoogleConsumerSecret)
#Get a Request Token
requestToken = gdClient.FetchOAuthRequestToken(scopes=_GoogleDataScope,
extra_parameters={'xoauth_displayname': APP_NAME})
#Persist token secret
self.session = Session()
self.session[TOKENSECRETKEY] = requestToken.secret
gdClient.auto_set_current_token = True
gdClient.SetOAuthToken(requestToken)
authUrl = gdClient.GenerateOAuthAuthorizationURL(callback_url=callbackURL)
self.redirect(authUrl)
I authenticated my domain with Google at https://www.google.com/accounts/ManageDomain, entering a target URL and am using the given Consumer Key/Secret. For instance, if my domain was 'juno.appspot.com', I am using http://juno.appspot.com as the target url path prefix.
The process is working; however, Google presents this message to the user in a yellow security box:
"The application that directed you
here claims to be 'xxxxxx'. We are
unable to verify this claim as the
application runs on your computer, as
opposed to a website. We recommend
that you deny access unless you trust
the application."
I don't think I should be getting this error, since my server is getting the request token and creating the authorization URL. Does anyone have any insight on how to get rid of this warning?
Google's domain registration has an option to upload a certificate, but I shouldn't need to do that because I'm using OAuth with the HMAC_SHA1 signature method.
Also, not that it should matter, but I'm doing all this through a UIWebView on the iPhone. I'm specifically trying to do all authentication server-side to avoid exposing my Consumer Key/Secret.
Thank you for any tips :)
Solved.
The culprit is this line from above:
extra_parameters={'xoauth_displayname': APP_NAME})
Setting this value for a registered application intentionally triggers a warning to users, as indicated by the Google documentation:
xoauth_displayname:
(optional) String identifying the
application. This string is displayed
to end users on Google's authorization
confirmation page. For registered
applications, the value of this
parameter overrides the name set
during registration and also triggers
a message to the user that the
identity can't be verified. For
unregistered applications, this
parameter enables them to specify an
application name, In the case of
unregistered applications, if this
parameter is not set, Google
identifies the application using the
URL value of oauth_callback; if
neither parameter is set, Google uses
the string "anonymous".
Removing this line no longer allows me to use a 'nice' name in place of the domain, but it gets rid of that annoying yellow box :)
I'm not sure exactly where the issue may be in your code, but I've got a one page oauth/appengine/gdata example which may at least set you in the right direction. Have you tried to navigate to the site directly from the iPhone/desktop browser to see what message is delivered?
Hope it helps.
Alternatively, is it possibly to do with the user agent the UIWebView sets?