Positively identify the current user from my javascript in SharePoint - javascript

When logging into SharePoint a cookie called "AADAuth" is set on ".office.com". The contents of this cookie is a JWT and it positively identifies the current user. If I could get this cookie from my javascript in SharePoint, I could send the JWT to my custom API and with Microsofts public certificate, I would be able to positively verify the identity of the user.
However, since this cookie is on ".office.com" which is obviously not my SharePoint domain, I cannot access that cookie.
So is there a way to get the JWT on SharePoint?
Otherwise, is there any other way I can find any content on the client side javascript to positively identify the current user?
I know I can initiate a new authentication process from my javascript in SharePoint, but that takes time and I'm looking for a solution that doesn't take much time for the end user, so hopefully I can use some of the information already in SharePoint.

I'm going to include a couple of different methods as it seems you may be looking for a specialized solution rather than a general one.
General Solution
I'm going to expand on DevBot's answer using _spPageContextInfo.userId, which is a number, not the person's user name.
<script type="text/javascript">
var spContextUserId;
$(document).ready(function() {
spContextUserId = _spPageContextInfo.userId;
console.log('user context id: ' + spContextUserId);
console.log('Executing sp.js...');
SP.SOD.executeOrDelayUntilScriptLoaded(loadSPUserProfiles, 'sp.js');
});
function loadSPUserProfiles() {
// ..wait for sp.js to load
console.log('sp.js loaded. Loading sp.userprofiles.js...');
SP.SOD.executeOrDelayUntilScriptLoaded(getUserProperties, 'SP.UserProfiles.js');
}
</script>
<script type="text/ecmascript">
var userProfileProperties;
function getUserProperties() {
try {
console.log('sp.userprofiles.js loaded...');
console.log('Getting user properties...');
var clientContext = new SP.ClientContext.get_current();
var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);
userProfileProperties = peopleManager.getMyProperties();
clientContext.load(userProfileProperties);
clientContext.executeQueryAsync(onRequestSuccess, onRequestFail);
}
catch (err) {
console.log(err.message);
}
}
function onRequestSuccess() {
console.log('in request success routine');
var accountName = "";
try {
//console.log(userProfileProperties);
//accountName = userProfileProperties.get_accountName; // also works
accountName = userProfileProperties.get_userProfileProperties()['AccountName'];
console.log('accountName from svc: ' + accountName);
// Now see if that account name matches the ID
getUserInfo(accountName, function(userInfo){
console.log('User Id: ' + userInfo.ID);
if (spContextUserId == userInfo.ID) {
alert('Verified');
} else {
alert('Not verified.');
}
},
function(sender,args){
console.log(args.get_message());
});
catch(ex) {
console.log(ex.message);
}
function getUserInfo(userName, Success, Error)
{
var context = new SP.ClientContext.get_current();
var userInfoList = context.get_web().get_siteUserInfoList();
var query = new SP.CamlQuery();
var viewXml = "<View> \
<Query> \
<Where> \
<Eq><FieldRef Name='UserName' /><Value Type='Text'>" + userName + "</Value></Eq> \
</Where> \
</Query> \
<RowLimit>1</RowLimit> \
</View>";
query.set_viewXml(viewXml);
var items = userInfoList.getItems(query);
context.load(items,'Include(Deleted,Department,EMail,FirstName,ID,IsActive,IsSiteAdmin,JobTitle,LastName,MobilePhone,Name,Notes,Office,Picture,SipAddress,UserName,WebSite,WorkPhone)');
context.executeQueryAsync(function() {
if(items.get_count() > 0) {
var item = items.itemAt(0);
Success(item.get_fieldValues());
}
else {
Success(null);
}
}, Error);
}
</script>
This getUserInfo function was posted at https://sharepoint.stackexchange.com/questions/31457/get-user-via-javascript-client-object-model , but I've used a similar construct, myself.
This takes the numeric ID assigned to a user by the page context and compares this against the People Manager version after getting the current web context. To me, this is about as effective as you're going to get using just the JSOM to verify that a user matches their login context, if we assume they are already on a SharePoint page and have been authenticated via Windows, as they should have been.
Tokens
If you are still trying to pursue getting the JWT or using a token method, or just otherwise need access to this path for some reason, say to make an add-in work or because they're accessing SharePoint from outside it, you're going to want to review Microsoft's documentation on what the tokens look like and how to create one, because using Context Token flow requires using the CSOM, not JSOM. There, you have to get a context token to get an access token:
SharePointContextToken contextToken =
TokenHelper.ReadAndValidateContextToken(contextTokenString,
Request.Url.Authority);
string sharePointUrl = WebConfigurationManager.AppSettings.Get("RedirectUri");
Response.Redirect(TokenHelper.GetAppContextTokenRequestUrl(sharePointUrl, Server.UrlEncode(Request.Url.ToString())));
where you have set up the RedirectUri in the web.config:
<configuration>
<appSettings>
<add key="RedirectUri" value="https://contoso.com/RedirectAccept.aspx" />
</appSettings>
<configuration>
Instead, you'd have to translate all of this into a JSOM method to match what you say you're trying to achieve, creating your own using "Authorization flow", and then you should be able to use it against your API.
In the Authorization Code flow, there is no context token, so you cache the refresh token itself, and the user gets a new refresh token each time he or she launches the add-in (https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/handle-security-tokens-in-provider-hosted-low-trust-sharepoint-add-ins#CacheKey). It must redirect the user to the SharePoint's OAuthAuthorize.aspx:
/oauthauthorize.aspx?IsDlg=1&client_id=c78d058c-7f82-44ca-a077-fba855e14d38&scope=list.read&response_type=code&redirect_uri=https%3A%2F%2Fcontoso%2Ecom%2Fredirectaccept.aspx
IsDlg=1 would launch an authentication dialog.
The client_id has to be the same as when your add-in got registered with SharePoint.
Perhaps this would work as a GET inside an AJAX call. Microsoft talks about redirecting the user to this URL, which indicates to me it would be a call to actually change the URL:
window.location.href = 'http://www.mysharepointsite.com/somesite/oauthorize.aspx?...';
My suggestion would be to review this documentation thoroughly and construct what they say you need/perform the redirects required using this information:
https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/handle-security-tokens-in-provider-hosted-low-trust-sharepoint-add-ins
https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/authorization-code-oauth-flow-for-sharepoint-add-ins
Without more information on how you are accessing SharePoint, and why you think the token method is the only way to verify an account, this is a difficult question to answer as it seems like you may have a specific scenario you are trying to build for, and there's often a lot of trial-and-error involved to ensure all permissions, GUIDs, etc. are registered and accurate.

You can grab _spPageContextInfo.userId or _spPageContextInfo.userLoginName.
More info about that object here.

Related

Auth0 unable to get ID token / user metadata

I am currently working on adding Auth0 to a Vue.js/Node.js application and so far I have figured out how to allow users to register and log in (to /callback) and that seems to be working fine. However, I have manually added (will be automatic later on) some data to the user metadata section. I have the below code as a rule that is turned on. I can’t seem to get access to the data on the Vue.js end of things. What I’d like is to be able to get the user data and user metadata so I can store it in my front end.
Rule code
function (user, context, callback) {
const namespace = 'account_signup_type/';
const namespace2 = 'account_type';
context.idToken[namespace + 'is_new'] = (context.stats.loginsCount === 1);
context.idToken[namespace2] = user.account_type;
context.idToken.user = user;
callback(null, user, context);
}
Code I am trying in my Vue.js front end
getIdTokenClaims(o) {
return this.auth0Client.getIdTokenClaims(o);
}
Currently, this returns undefined
I ended up figuring it out, there was no namespace being created in the id_token which resulted in it not properly passing the data through to the Vue .js app. I added a namespace using a web address format with the domain extension cut off and it now works.

View Facebook events outside of Facebook - where to put the access token?

Unfortunately, there are many snippets, but no complete code examples. I'm trying to allow my group's events to be seen in a webpage outside of FB, and following the Javascript SDK example quick start I'm able to put the login and share buttons. But when I try to access my group's events, I get the error - "An access token is required to request this resource." Where do I put the access token in this code?
window.fbAsyncInit = function() {
FB.init({
appId : '{myappID}',
xfbml : true,
version : 'v2.8'
});
FB.AppEvents.logPageView();
FB.api(
'/myGroupID/events',
'GET',
{},
function(response) {
// Insert your code here
}
);
};
A couple of things:
I don't need my users to log in, as my group is public; my events are public. If someone is casually browsing through my website I want them to be able to see the events.
One of the other things that I've had trouble with is extremely short answers. Coming from the .NET community, I'm used to seeing lots of tutorials, and lots of complete code examples for how to do things. There are few "long form" answers or tutorials - even stackoverflow answers (like this one) don't contain enough detail on how to do this stuff. Especially the access token thing.
Is there a complete example of how to do this?
Thanks in advance.
You COULD add the Token like this:
FB.api(
'/myGroupID/events',
'GET',
{access_token: 'xxx'},...
...but that would expose your Token (which is always meant to be kept secret) to everyone visiting the website. You have to do that API call server side. Check out the PHP SDK or just use PHP cURL. The Token is just a GET parameter.
Ok, I figured it out. The point of all of this is to get the Access Token that Facebook says it wants. The Access Token is really the appsecret_proof (and not the access_token - the access_token is a different thing. See later in this post), so be aware of that. I cobbled together some different code examples (like this one, and a super, very careful reading of the Facebook graph api docs, to reach an answer. I coded in C# rather than Javascript because a lot of this needs to be done server side, and I'm more comfortable there anyway.
I created a console app as a proof of concept. A few notes:
the page_id is, in my case, for a group, not a (capital P) Page,
which is a different thing.
The access_token and app_secret are from the app
you've (hopefully) already created. (If you need to get the access_token in the first place, there
are some docs out there to help you get this part started.) You should also
make sure to use (as of .NET 1.0) the dotnet core secret manager
to protect your secrets when you develop so you don't pass them
around via source control.
This last one is big - the appsecret_proof is a combination of your access_token and your app_secret hashed together (with the app_secret being the key) and then made part of the query string. This is what Facebook wants when it says "An access token is required to request this resource."
public static string page_id = {your_page_id};
public static string access_token = {your_app_access_token};
public static string app_secret = {your_app_secret};
public static string appsecret_proof = FaceBookSecret(access_token, app_secret);
static void Main(string[] args)
{
Console.WriteLine("Making API Call...");
using (var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }))
{
client.BaseAddress = new Uri("https://graph.facebook.com/" + page_id + "/");
HttpResponseMessage response = client.GetAsync("events?access_token=" + access_token + "&appsecret_proof=" + appsecret_proof).Result;
response.EnsureSuccessStatusCode();
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("Result: " + result);
}
Console.ReadLine();
}
internal static string FaceBookSecret(string content, string key)
{
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
byte[] messageBytes = Encoding.UTF8.GetBytes(content);
byte[] hash;
using (HMACSHA256 hmacsha256 = new HMACSHA256(keyBytes))
{
hash = hmacsha256.ComputeHash(messageBytes);
}
StringBuilder sbHash = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sbHash.Append(hash[i].ToString("x2"));
}
Console.WriteLine(sbHash);
return sbHash.ToString();
}
So after all of this runs, I get a nice json string with all of my events.

Authorizing xAPI interactions through Javascript triggers against my LRS

I've constructed a course in Storyline2, and defined several triggers via javascript as xAPI (tincan) activities.
I have an LRS endpoint link and authorization token, but I'm yet to understand where these credentials should be embedded in order for the whole thing to function properly.
I was given this at another message board:
var tincan = new TinCan({url: window.location.href});
Which of these needs to be replaced by one of the above mentioned? I'm guessing another should be added via "+" after "href".
That example code (which I guess you got from the article I wrote here: http://tincanapi.com/share-statements-between-courses/ ) is envisaging that you will launch the Storyline content from something like an LMS. In that case you will enter the endpoint and authorization details in the LMS settings, and the LMS will pass those to Storyline.
See: http://tincanapi.com/share-statements-between-courses/
If you want to put the details directly in the package, see the example code here: http://rusticisoftware.github.io/TinCanJS/
(included below for convenience)
var lrs;
try {
lrs = new TinCan.LRS(
{
endpoint: "https://cloud.scorm.com/tc/public/",
username: "<Test User>",
password: "<Test Password>",
allowFail: false
}
);
}
catch (ex) {
console.log("Failed to setup LRS object: " + ex);
// TODO: do something with error, can't communicate with LRS
}

Can I detect valid JSONP return?

I want to send/receive data to a web service on a different server that does not have CORS enabled. I have to use JSONP to do it. The service requires authentication and most of the time the user is in an SSO environment and the SSO mechnaism easily passes them to the service without issue.
When not inside the SSO environment, the user is redirected (301) to a login page, which of course returns invalid javascript because the file would start with <!Doctype...
Is there a way to see if the returned data is operable? I know exactly what the first 15 chars should be anytime the user can successfully access the service. I would like to check the return and then do something like:
if(dataIsValid){ //continue normally, build page with returned data }
else{ //redirect to non-SSO login page }
Or anything that resembles that kind of a workflow.
I assume you're using jQuery since you've added a relevant tag.
var dataIsValid = true;
var data = {};
try {
data= $.parseJSON(data);
} catch (exc){
dataIsValid = false;
}
//...

How to set access token on analytics API?

i was wondering, the documentation has tutorials for implementing use of the Analytics API in several languages.
Now in PHP they show how to store the access token and maintain it , now i assume the JS somehow mentains it in some sort of local storage but i don't wish the user to authenticate each time he visitis so my plan is to save the access & refresh token in my database and simply applying it to the client-side instead of going through the all pop up procress.
According to tutorial this :
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, result);
Fires the popup which returns the access token but i'll say again i'm intrested in submiting token from database.
How can that be done?
is there some gapi.auth.setToken(token) method which after i could make calls to Core Reporting API?
I arrived here looking for a solution using this after having already written PHP to do the auth using google's PHP client libraries. I wanted to share the stored token as you mentioned, and be able to use javascript without re-authenticating, and/or triggering the popup (there may be a solution to this using the .init(callback) method, see docs/links at end).
Is there some gapi.auth.setToken(token) method
It turns out you can, there is the exact setToken(token) function you mention, and, you can even share the auth token generated earlier in PHP. What I'm not sure about yet, is if we should do it :)
I'm using PHP to do the initial auth, but presumeably in the javascript client you'd be able to call setToken() on something that you'd stored with getToken() in the same manner as this example. There may also be better approaches to this, like CORS (see links at end) mentioned in the API Authentication docs that I haven't had a chance to investigate any of these yet, but, I can give an example to answer the question, and might be useful to others needing the same behaviour
I first found Google developer Dan Holevoet's blog post with some sample JS code.
http://googleappsdeveloper.blogspot.com.au/2011/12/using-new-js-library-to-unlock-power-of.html
It's great to be able to query the API directly with javascript, and dynamically load lists etc, but the thing that worried me about this of course was storing clientid etc in js..
// Snippet from Dan's post
var clientId = 'YOUR_CLIENT_ID';
var apiKey = 'YOUR_API_KEY';
var scopes = 'https://www.googleapis.com/auth/calendar';
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
checkAuth();
}
But, according to Dan in an answer to the same question:
The apiKey is used in conjunction with designated referrers, which you
must declare in the APIs Console. If the key is sent from an
unauthorized referrer, it will not work. You should make your accepted
referrer list as limited as possible to ensure that others do not use
your apiKey for their own requests.
Now, my example is for the calendar API, but it all seems pretty consistent with the other APIs as well.
Note: This snippet was for proof-of-concept purposes only, and probably shouldn't be used in production. I assume the referrer protection mentioned makes something like this OK to do, but more thought needs to be given. It could be done by hidden input, AJAX call etc.. But in the end, they're all going to be visible in the javascript.
What I did to test the concept was to:
Authenticate via PHP client libs, using the same script/URL as callback (see [API console][2] for these callbacks)
On sucessful auth, in callback, set a global javascript variable in the page to the stored PHP auth token
Once page is loaded, on your first click event to use the javascript (or even in document ready), call authMe() to set the token
Then proceed as normal, calling any javascript API methods that you have given scope to in the initial PHP authentication process (in this case makeApiCall())
Like so:
In the php callback routine, regardless of whether authenticated yet (assuming that your callback URL is the same script), make this var global
<script type="text/javascript">
// A place to stick PHP's auth token once the auth dance is done
var dodgey_global_access_token = {};
</script>
Now, in php callback routine once we've checked that we're authenticated, and $_SESSION['token'] = $client->getAccessToken(); has been called (IE storing the auth token somewhere for later), or at least $client->getAccessToken() has something meaningful:
<script type="text/javascript">
// Set the js var via PHP here.. Yeck... Note json encode in php and parse in jquery
dodgey_global_access_token = $.parseJSON (<?php echo json_encode ($client->getAccessToken() ); ?>);
// dodgey_global_access_token now contains the auth token structure
// Removed auth-related functions in Dan's code - we'll already have a token
// Dan's orig function to list events in 'primary' calendar
function makeApiCall() {
gapi.client.load('calendar', 'v3', function() {
var request = gapi.client.calendar.events.list({
'calendarId': 'primary'
});
request.execute(function(resp) {
for (var i = 0; i < resp.items.length; i++) {
var li = document.createElement('li');
li.appendChild(document.createTextNode(resp.items[i].summary));
document.getElementById('events').appendChild(li);
}
});
});
}
// My function to setToken with the token injected from PHP authentication
function authMe () {
// Stuff the token into the gapi object
gapi.auth.setToken( dodgey_global_access_token );
// Now call the original 'makeAPICall' function now that we're 'authenticated'
makeApiCall();
}
</script>
Note: I used jquery in my example to quickly parse the JSON, we're already using it in the project, but if not, you'll have to find another library to do so
Relevant/useful docs:
// Sorry, as a new poster I can only post 2 hyperlinks,
// so embedding this in code snippet
//
// http://code.google.com/p/google-api-javascript-client/wiki/ReferenceDocs#gapi
// http://code.google.com/p/google-api-javascript-client/wiki/Authentication
// http://code.google.com/p/google-api-javascript-client/issues/detail?id=22
// http://code.google.com/p/google-api-javascript-client/wiki/CORS
// https://code.google.com/apis/console
Let me know if anything's unclear, and can post a working sample to demo.
Google API documentation has changed a bit from when this answer was first provided. google-api-javascript-client is no longer the recommended library for accessing google api libraries and Google suggests using discovery documents to load specific libraries.
As a result, the workflow that you are looking for in order to load all the libraries and set the access token would look like this:
var token = 'someaccesstokenfromoauth2'
gapi.load('client:auth2', function(){
gapi.client.load(
'https://analyticsreporting.googleapis.com/$discovery/rest',
'v4'
).then(function(){
gapi.auth.setToken({ access_token: token })
// business logic with gapi.client.analyticsreporting()
})
})
Load gapi with:
<script src="https://apis.google.com/js/api.js"></script>

Categories

Resources