We are trying to invoke the TFS 2015 REST API's from a web-page using Javascript and have a challenge in establishing valid authentication with the TFS server.
We do not know how to generate a personal access tokens or an OAuth access tokens. The instruction below seem to apply more toward VSO than on-premise TFS.
https://www.visualstudio.com/en-us/integrate/get-started/rest/basics
How can I generate an authentication key/token?
UPDATE: As on Mar 2017, the latest release of On-Prem TFS supports creating personal access tokens for all users. Using the below javascript code by #Elmar you can make requests to update, edit TFS workitems from REST API.
The OAuth mechanism is used against the VSO api at the time of writing this as you've seemingly identified. official docs for VSO OAuth tokens here.
For on-prem however, the following is required:
Via a javascript client (note I'm using jquery for the ajax request here)
Since alternative creds or token based auth isn't available on-prem to match current vso implementation; You can consider the following approach: If you have admin permissions on the TFS app tier, you can configure basic authentication for the tfs application in IIS, and set the default domain.
And then invoke as follows:
var self = this;
self.tasksURI = 'https://<SERVER>/tfs/<COLLECTION>/<PROJECT>/_apis/build/builds?api-version=2.0';
self.username = "<USERNAME>"; //basic username so no domain here.
self.password = "<PASSWORD>";
self.ajax = function (uri, method, data) {
var request = {
url: uri,
type: method,
contentType: "application/json",
accepts: "application/json",
cache: false,
dataType: 'json',
data: JSON.stringify(data),
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password));
},
error: function (jqXHR) {
console.log("ajax error " + jqXHR.status);
}
};
return $.ajax(request);
}
self.ajax(self.tasksURI, 'GET').done(function (data) {
alert(data);
});
IMPORTANT NOTE! : If you enable basic auth you really should configure your site to use https too or your credentials will be sent in clear text (as indicated in the warning seen -> top right of the image above).
Via a .NET client
In on-prem (currently rtm'd: 2015 update 1) the api is generally gated/fenced off with NTLM, meaning a pre-flight request is made, 401 returned from server to challenge for auth, in this case, setting the request Credential as follows allows the request to auth against the server once the preflight challenge is received.
To accommodate the challenge you can do this:
request.Credentials = new NetworkCredential(this.UserName, this.Password);
//you may want to specify a domain too
If you've enabled basic auth for tfs on prem you can attempt authenticating as follows, this pattern matches the mechanism used when invoking vso after enabling alternative credentials in the ui:
request.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(this.UserName + ":" + this.Password));
Note: In some code I modified a few weeks ago; support for both VSO and on-prem was required so I used the two patterns above to deal with the specific scenario.
My question is old but as on Mar 2017, the latest release of On-Prem TFS supports creating personal access tokens for all users. Using the javascript code by #Elmar you can make requests to update, edit TFS workitems from REST API.
If possible, I would recommend using the .NET client libraries for Visual Studio Team Services (and TFS):
https://www.visualstudio.com/en-us/docs/integrate/get-started/client-libraries/dotnet
You can use windows authentication (which is what I needed). After including the following nuget packages in my code:
Microsoft.TeamFoundationServer.Client
Microsoft.VisualStudio.Services.Client
Microsoft.VisualStudio.Services.InteractiveClient
I was able to write this code:
// Create instance of VssConnection using Windows credentials (NTLM)
var connection = new VssConnection(new Uri("http://mytfsserver:8080/tfs/CollectionName"), new VssClientCredentials());
// Create instance of WorkItemTrackingHttpClient using VssConnection
var gitClient = connection.GetClient<GitHttpClient>();
var items = gitClient.GetRepositoriesAsync().Result;
foreach (var item in items)
{
Console.WriteLine(item.Name);
}
See also: https://www.visualstudio.com/en-us/docs/integrate/get-started/client-libraries/samples
Related
I'm trying to migrating my authentication method from Power BI Master User to service principal.
on master user I'm using msal with authentication flow like bellow:
login to AAD --> request for AAD token --> importing pbix file with rest API using AAD token as credential
this is the code
$(document).ready(function () {
myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
acquireTokenPopup();
});
Msal.UserAgentApplication
});
function acquireTokenPopup() {
myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
AADToken = tokenResponse.accessToken;
importPBIX(AADToken);
});
}
function importPBIX(accessToken) {
xmlHttp.open("GET", "./importPBIX?accessToken=" + accessToken + "&pbixTemplate=" + pbixTemplate, true);
//the rest of import process//
}
so there are two question:
1. what kind of flow would it be if I use service principal instead?
on my head and from the info which I read from microsoft document it would be simpler like:
request token using application secret key --> importing pbix file with rest API using token
is this correct?
2. what kind of code that I can use to do this on javascript?I think MSAL couldn't do token request by using service principal. would appreciate any info or tutorial for this.
bests,
what kind of flow would it be if I use service principal instead? on my head and from the info which I read from microsoft document it would be simpler like: request token using application secret key --> importing pbix file with rest API using token is this correct?
According to my research, if you want to use the service principal to get Azure AD access token, you can use the client credentials grant flow
The client application authenticates to the Azure AD token issuance endpoint and requests an access token.
The Azure AD token issuance endpoint issues the access token.
The access token is used to authenticate to the secured resource.
Data from the secured resource is returned to the client application.
Regarding how to get access token, please refer to the following steps
Register Azure AD application
Configure API permissions
Get access token
POST https://login.microsoftonline.com/<tenant id>/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=<>
&client_secret=<>
&resource=https://analysis.windows.net/powerbi/api
2. what kind of code that I can use to do this on javascript?I think MSAL couldn't do token request by using service principal. would appreciate any info or tutorial for this.
If you want to implement client credentials grant flow with sdk, you can use adal-node. For more details, please refer to https://www.npmjs.com/package/adal-node.
For example
var AuthenticationContext = require('adal-node').AuthenticationContext;
var authorityHostUrl = 'https://login.microsoftonline.com/';
var tenant = 'myTenant.onmicrosoft.com'; // AAD Tenant name.
var authorityUrl = authorityHostUrl + '/' + tenant;
var applicationId = 'yourApplicationIdHere'; // Application Id of app registered under AAD.
var clientSecret = 'yourAADIssuedClientSecretHere'; // Secret generated for app. Read this environment variable.
var resource = ''; // URI that identifies the resource for which the token is valid.
var context = new AuthenticationContext(authorityUrl);
context.acquireTokenWithClientCredentials(resource, applicationId, clientSecret, function(err, tokenResponse) {
if (err) {
console.log('well that didn\'t work: ' + err.stack);
} else {
console.log(tokenResponse);
}
});
thanks to Jim's answer, I've tweaked my code a little bit and the token authentication process went smoothly.
As my apps using javascript at front-end and python as its back-end, I decided to do the process at python and used python msal library instead.
the code is just like :
authority_host_uri = 'https://login.microsoftonline.com'
tenant = 'myTenantId'
authority_uri = authority_host_uri + '/' + tenant
client_id = 'myClienId'
client_secret = 'myClientSecretKey'
config={"scope":["https://analysis.windows.net/powerbi/api/.default"]}
app = ConfidentialClientApplication(client_id, client_credential=client_secret, authority=authority_uri)
token = app.acquire_token_for_client(scopes=config['scope'])
once again thanks to Jim for helping me on this one.
bests,
I started implementing 2 years ago my custom authentication in azure mobile service with node.js to use it in my jquery/cordova appery.io app. I have a database with users, passwords, etc and a login api in node.js backend that generates a valid jwt like this:
http://www.thejoyofcode.com/Exploring_custom_identity_in_Mobile_Services_Day_12_.aspx
http://chrisrisner.com/Version-1-of-the-Mobile-Services-JWT-token-has-been-deprecated
But now i have been migrated to azure app service and don´t know how to implement custom authetication with javascript.
At the examples above:
"iss":"urn:microsoft:windows-azure:zumo",
"ver":2,
"aud":aud,
But i have read that "aud" and "iss" must be my azure website and "sub" my own userId, to generate a valid jwt in my custom auth to use it in azure. Is it right?
https://shellmonger.com/2016/04/08/30-days-of-zumo-v2-azure-mobile-apps-day-5-custom-authentication/
The important thing is the token. There are some requirements for using 3rd party tokens with an Azure Mobile App:
It must follow the Json Web Token format
The user Id must be in the subject (sub) field
The audience (aud) and issuer (iss) must be known and configured
I do a call to my login API with ajax post and obtain a valid JWT token (http://jwt.io), but i don´t know the correct process to login and use azure services because i don´t know the correct way to use it on my app. I have changed MobileServices.Web.min.js to azure-mobile-apps-client.js in the app.
https://github.com/Azure/azure-mobile-apps-js-client
Can i use the jwt token created in azure node.js or exchange it for a session token and use client.login() method by JS SDK?
EDIT:
I see the light again adding to ajax POST
headers: {'X-ZUMO-AUTH': usuarioToken},
So my "authenticated users only" api works again with the old MobileServices.Web.min.js! It also works perfect with invokeApi method:
var mobileClient = new WindowsAzure.MobileServiceClient(urlApp,keyApp);
mobileClient.currentUser = {
userId: myCustomUserId,
mobileServiceAuthenticationToken: myCustomToken
};
mobileClient
.invokeApi('data', {
method: 'POST',
headers: {'X-ZUMO-AUTH': myCustomToken},
body: JSON.stringify(theData)
}) //end invokeApi
.done(
function (response) {
alert(response);
},
function (error) {
alert("error"));
}
); //end done
The mobileClient generated contains this data:
mobileClient= {"applicationUrl":"https://xxx.azure-mobile.net/","applicationKey":"hGhzxxx","version":"ZUMO/1.2 (lang=Web; os=--; os_version=--; arch=--; version=1.2.21003.0)","currentUser":{"userId":"Custom:25F600BB-xxxx-xxxx-xxxx-8329BCCF31D2","mobileServiceAuthenticationToken":"ey__rest of jwt token"},"_serviceFilter":null,"_login":{"_loginState":{"inProcess":false,"cancelCallback":null},"ignoreFilters":true},"push":{"_apns":null,"_gcm":null,"_registrationManager":null}}
I will try again wiht the new azure-mobile-apps-client.js
Any comment will be welcome
I've been stuck for the whole day making my first call of Azure Storage REST API. Postman's response showed that it's due to error in Azure authentication, but I have no idea what's the problem.
Here is the browser script to send Azure Storage REST API:
function azureListContainers() {
var key = "key-copied-from-azure-storage-account";
var strTime = (new Date()).toUTCString();
var strToSign = 'GET\n\n\n\nx-ms-date:' + strTime + '\nx-ms-version:2015-12-11\n/myaccount/?comp=list';
var hash = CryptoJS.HmacSHA256(strToSign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
var auth = "SharedKeyLite myaccount:"+hashInBase64;
console.log(strToSign);
console.log(auth);
console.log(strTime);
$.ajax({
type: "GET",
beforeSend: function (request)
{
request.setRequestHeader("Authorization", auth);
request.setRequestHeader("x-ms-date", strTime);
request.setRequestHeader("x-ms-version", "2015-12-11");
},
url: "https://myaccount.blob.core.windows.net/?comp=list",
processData: false,
success: function(msg) {
console.log(msg);
}
});
}
Chrome Developer Tool just returned No 'Access-Control-Allow-Origin' header without further reason, so I copied the contents of var auth and var strTime, created the same request using Postman tool:
[Command]
GET https://myaccount.blob.core.windows.net/?comp=list
[Headers]
Authorization:SharedKeyLite myaccount:Z9/kY/D+osJHHz3is+8yJRqhj09VUlr5n+PlePUa8Lk=
x-ms-date:Tue, 09 Aug 2016 10:30:49 GMT
x-ms-version:2015-12-11
[Response Body]
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:9be3d595-0001-0012-4929-f2fde2000000
Time:2016-08-09T10:31:52.6542965Z</Message>
<AuthenticationErrorDetail>The MAC signature found in the HTTP request 'Z9/kY/D+osJHHz3is+8yJRqhj09VUlr5n+PlePUa8Lk=' is not the same as any computed signature. Server used following string to sign: 'GET
x-ms-date:Tue, 09 Aug 2016 10:30:49 GMT
x-ms-version:2015-12-11
/myaccount/?comp=list'.</AuthenticationErrorDetail>
</Error>
After diff the two strings, I believe var strToSign in my script is the same as the string Azure used to sign. But still there was an authentication error. Please help indicate what's the problem.
Chrome Developer Tool just returned No 'Access-Control-Allow-Origin' header
As you are using javascript to send http requests against to Azure Storage Server directly from client. It's a common CORS issue.
When you occur this issue, you can enable the CORS for your storage services. For simple, you can leverage Microsoft Azure Storage Explorer to configure your storage.
AuthenticationFailed
I did two modifications based on your code snippet, in my test project to make it work.
var hash = CryptoJS.HmacSHA256(strToSign, key); The second parameter, should be a base64 decode from the account key, refer to the Azure Storage SDK for node.js
In my test, I had to use SharedKey scheme and authenticate with SharedKey token to make your scenario to work.
Here is my test code snippet, FYI.,
var key = "key-copied-from-azure-storage-account";
var strTime = (new Date()).toUTCString();
var strToSign = 'GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:' + strTime + '\nx-ms-version:2015-12-11\n/<accountname>/\ncomp:list';
var secret = CryptoJS.enc.Base64.parse(key);
var hash = CryptoJS.HmacSHA256(strToSign, secret);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
var auth = "SharedKey <accountname>:"+hashInBase64;
Additionally, for security reason, please implement the Azure Storage Services in backend web server. As using pure javascript on client, will expose your account name and account key to public, which will raise the risk to your data and info.
I am building a single page app with JavaScript to access a users OneNote notebooks.
Using this git project as a starting point: https://github.com/OfficeDev/O365-Angular-Microsoft-Graph-Connect
I set up the app in Azure AD with full permissions to MS graph.
I can login and get a bearer token, however I can't pull any information from my OneNote notebooks using this endpoint:
graph.microsoft.com/beta/me/notes/notebooks.
Here's my function:
function connectToOneNote(){
var request = {
method: 'GET',
url: 'https://graph.microsoft.com/beta/me/notes/notebooks',
};
// Execute the HTTP request.
$http(request)
.then(function (response) {
$log.debug('HTTP request to Microsoft Graph API returned successfully.', response);
response.status === 202 ? vm.requestSuccess = true : vm.requestSuccess = false;
vm.requestFinished = true;
}, function (error) {
$log.error('HTTP request to Microsoft Graph API failed.');
vm.requestSuccess= false;
vm.requestFinished = true;
});
};
I get this error: "The OneDriveForBusiness for this user account cannot be retrieved."
However when using the endpoint in the graph explorer: https://graph.microsoft.io/en-us/graph-explorer, my notebooks are retrieved without issue.
Any ideas?
Thanks for the responses. The answer for me was pretty simple. I had added the app to a newly created AD, which didn't have an O365 account associated with it. I was logging in as a global admin with an active O365 account on a different AD, but because it was a global admin, it had permissions on all AD instances. SMH.
I'm in the process of designing an API in PHP that will use OAuth2.0. My end goal is to build a front-end application in javascript (using AngularJS) that accesses this API directly. I know that traditionally there's no way to secure transactions in javascript and so directly accessing an API isn't feasible. The front-end would need to communicate with server code that in turn communicated with the API directly. However, in researching OAuth2 it looks as if the User-Agent Flow is designed to help in this situation.
What I need help with is implementing the OAuth2 User-Agent Flow in javascript (particularly AngularJS if possible as that's what I'm using for my front-end). I haven't been able to find any examples or tutorials that do this. I really have no idea where to start and don't want to read through the entire OAuth2 spec without at least seeing an example of what I'll be looking at doing. So any examples, tutorials, links, etc would be greatly appreciated.
The Implicit Grant flow (the one you're referring to as User-Agent Flow) is exactly the way to go:
The implicit grant is a simplified authorization code flow optimized for clients implemented in a browser using a scripting language such as JavaScript.
To understand the flow, the documentation from Google for client-side applications is a really good place to start. Note that they recommend you to take an additional token validation step to avoid confused deputy problems.
Here is a short example implementation of the flow using the Soundcloud API and jQuery, taken from this answer:
<script type="text/javascript" charset="utf-8">
$(function () {
var extractToken = function(hash) {
var match = hash.match(/access_token=([\w-]+)/);
return !!match && match[1];
};
var CLIENT_ID = YOUR_CLIENT_ID;
var AUTHORIZATION_ENDPOINT = "https://soundcloud.com/connect";
var RESOURCE_ENDPOINT = "https://api.soundcloud.com/me";
var token = extractToken(document.location.hash);
if (token) {
$('div.authenticated').show();
$('span.token').text(token);
$.ajax({
url: RESOURCE_ENDPOINT
, beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', "OAuth " + token);
xhr.setRequestHeader('Accept', "application/json");
}
, success: function (response) {
var container = $('span.user');
if (response) {
container.text(response.username);
} else {
container.text("An error occurred.");
}
}
});
} else {
$('div.authenticate').show();
var authUrl = AUTHORIZATION_ENDPOINT +
"?response_type=token" +
"&client_id=" + clientId +
"&redirect_uri=" + window.location;
$("a.connect").attr("href", authUrl);
}
});
</script>
<style>
.hidden {
display: none;
}
</style>
<div class="authenticate hidden">
<a class="connect" href="">Connect</a>
</div>
<div class="authenticated hidden">
<p>
You are using token
<span class="token">[no token]</span>.
</p>
<p>
Your SoundCloud username is
<span class="user">[no username]</span>.
</p>
</div>
For sending XMLHttpRequests (what the ajax() function does in jQuery) using AngularJS, refer to their documentation of the $http service.
If you want to preserve state, when sending the user to the authorization endpoint, check out the state parameter.
There's an example of Authorization Code Grant approach to get a token from OAuth server. I used jQuery ($) to make some operations.
First, redirect user to authorization page.
var authServerUri = "http://your-aouth2-server.com/authorize",
authParams = {
response_type: "code",
client_id: this.model.get("clientId"),
redirect_uri: this.model.get("redirectUri"),
scope: this.model.get("scope"),
state: this.model.get("state")
};
// Redirect to Authorization page.
var replacementUri = authServerUri + "?" + $.param(authParams);
window.location.replace(replacementUri);
When one gave authorization pass to get token:
var searchQueryString = window.location.search;
if ( searchQueryString.charAt(0) === "?") {
searchQueryString = searchQueryString.substring(1);
}
var searchParameters = $.deparam.fragment(searchQueryString);
if ( "code" in searchParameters) {
// TODO: construct a call like in previous step using $.ajax() to get token.
}
You could implement the Resource Owner Password Credentials Grant in the same manner using jQuery or pure XMLHttpRequest and don't make any redirects - because on each redirect you'll loose state of your application.
For me, I used HTML5 local storage to persist state of my application for data which were not likely to pose a security threat.