This article is part of a series that covers Visual Studio Team System (VSTS) extensions. In the previous two articles of the series, we have seen how to create a VSTS extension and then how to package, publish and install a VSTS extension.
In the packaging and publishing a VSTS extension article, I discussed two different ways to Package, Publish and Install the VSTS Extension. The first was by directly using the marketplace URL to upload the extension and install it. The second was to use a readymade extension provided by Microsoft that creates a task in the build definition which in turn packages, and publishes the targeted extension.
Let us now what happens after the extension is installed. Once the extension is installed on your VSTS account, it needs to be authorized so that it can access your VSTS data. This is in line with the OAuth 2.0 protocols. This VSTS data can be account information, work items data, source control data or build related data. After authorization, VSTS returns an Access Token which can be used for accessing VSTS data subsequently.
This Access Token is returned back to a callback function which should then store it securely.
This article will discuss how to call the authorization URL, and how to implement the authorization call back function to get the Access Token.
Azure - Authentication and Authorization
In our VSTS extension, we have used OAuth 2.0 which is a token based authentication and authorization mechanism. It allows user account related information (in our case VSTS account) to be made available to third party services (in our case the VSTS extension that we have written). The password provided by the user will not be made available to the third-party service, so it is not compromised.
One important thing to remember is that the access token provided is temporary (with an expiration date and time), and needs to be refreshed if expired. We also get a refresh token which is used to get a new access token, when the previous token has expired.
Real World Example
When we were creating our product TaskO for Visual Studio Team Services (VSTS), we realized that we have to access work items from the VSTS account, and create Service hooks (more on service hooks in the next article). In order to achieve this, we had to get authorization from users and then safely store their access token to subsequently access their VSTS account. As part of this authorization process, authorization APIs of VSTS made a call to our callback function. In our case, it is an Azure Function which will work as the authorization call back. This callback function will then use a code passed to it to get an access token for the VSTS account.
Note: Here are some pre-requisites:
- VSTS Account
- Access to Azure Portal (for creating call back azure function)
- VSTS Extension via .vsix package
- Organization’s web site, privacy statements to be provided while specifying authorization page
Let us do a complete walkthrough.
1. First register your VSTS extension. Enter url https://app.vssps.visualstudio.com/app/register to register the application. Provide company details like name, web site, url for services, url for privacy agreement. Followed by giving information about the application. This comprises of application name, description for the application, web site and call back url (Azure Function in our case).
2. Now specify scope for the application. In our case (TaskO), we needed the project scope and work item scope for read and write, as we wanted to register service hooks for each team project programmatically, and to create and update work items using that service hook.
3. After successful completion of all the required features, we got a client id for our company. We also got the application secret key. Keep it safe as we will need it to use in the call back function.
4.The next step is to create a call back function in azure. I have already discussed how to create Azure Function in this article.
5. Let us assume that the extension is installed by the user for a VSTS account. A tab to access the extension gets added to every project in the account. This tab contains a menu item to access the UI of the extension.
Figure 1: Tab for our extension added to each project in VSTS account after installation
When the user tries to open the user interface for the extension, an azure function (GetTasks) gets called. This call to azure function is made in VSS.Ready() method using ajax. Here’s a code snippet for the same which gets added to the html file for the extension.
VSS.ready(function ()
{
var collection = VSS.getWebContext().collection.name
var projectName = VSS.getWebContext().project.name;
var projId = VSS.getWebContext().project.id;
$.ajax(
{
dataType: "json",
async: false,
contentType: "application/json; charset=utf-8",
headers: {
Accept: "application/json",
"Access-Control-Allow-Origin": "*"
},
type: "GET",
data: "",
url: "https://<function url>/api/<method name>?collName=" + collection + "&projName=" + projectName + "&projId=" + projId,
success: function (data)
{
var tasksList = document.getElementById("listTasks");
<code if the function is successful>
}
},
error: function (xhr, ajaxOptions, thrownError)
{
document.getElementById("div1").innerHTML = xhr.readyState + " Error: " + thrownError ;
}
}
);
})
Observe how the values for collection (VSTS account name), project name and project id is retrieved and sent to azure function. The values are obtained by VSS.getWebContect() and sent via parameters to the method.
6. This method gets information about account name, project name and project id. The first thing the GetTasks azure function does is to find if there is an access token available for this account. If the user is accessing the extension for the first time, it does not find the access token. If the access token is not found, it means that VSTS Account owner has not authorized the extension to access required data. In such a case, a link is shown in the extension interface which asks the user if he/she wants to go to authorization page.
Figure 2: Link to authorization page as a part of button click
7. If the user clicks on the link for the authorization page, a page is displayed to the user by Microsoft for authorizing the extension. On this page, it is mentioned that the authorization is required for work items and project info.
Figure 3: Authorization page created by Microsoft for our extension
8. When the user clicks on “Accept”, a call is made to our authorization call back function. This function gets “code” as parameter (along with the state) which is sent by the authorization page. The function calls a method which in turn generates access token for the user by using the code.
State is a variable that is sent by the extension to the authorization page, which is just forwarded by that page to our callback function.
In order to create the token, we also need the app secret we safely stored earlier. We are using a mechanism where the token once received is kept in a file in an encrypted manner (we are using Azure File Storage in order to avoid any loss of data if we keep it with us). When the user tries to access again, the token stored is validated against the interval of seconds the token is valid. If it is still valid, the same token is used. If not, refresh token is automatically generated and used.
9. When the user tries to access GetTasks() method subsequently, it will detect that the access token already exists and goes with further process of providing the necessary data to the user.
10. In this whole scenario, there is one more step that of creating service hooks which I will discuss in next article.
Getting the Access Token
Let us see the algorithm of getting this token. The GetToken() function contains the following parameters: code which is coming from the authorization page, refresh as true or false and the last parameter is an object of TraceWriter that writes log onto azure log files (just for debugging purpose).
The tokenUrl is as shown below. The redirectUrl is the url which will point to our call back function written in azure. The urlData will be generated based on the refresh value. The same urlData is used to create oauthToken.
public static string GetToken(string code, bool refresh, TraceWriter log)
{
string tokenUrl = "https://app.vssps.visualstudio.com/oauth2/token";
string appSecret = <app secrete we have stored we created earlier>
string redirectUrl = https://<url for Authorization Call back function>";
string urlData = string.Empty;
if (refresh)
{
urlData = string.Format("client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}&grant_type=refresh_token&assertion={1}&redirect_uri={2}",
Uri.EscapeUriString(appSecret),
Uri.EscapeUriString(code),
redirectUrl);
}
else
{
urlData = string.Format("client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={1}&redirect_uri={2}",
Uri.EscapeUriString(appSecret),
Uri.EscapeUriString(code),
redirectUrl);
}
string responseData = string.Empty;
string oauthToken = "";
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(tokenUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (StreamWriter sw = new StreamWriter(request.GetRequestStream()))
{
sw.Write(urlData);
}
HttpWebResponse response = (HttpWebResponse)(request.GetResponse());
oauthToken = new StreamReader(response.GetResponseStream()).ReadToEnd();
}
catch (Exception ex)
{
log.Info("Exception:" + ex.Message);
}
return oauthToken;
}
This GetToken() method will be called as and when required.
The complete call back function in Azure looks as follows. The req parameter which is of type HttpRequestMessage is used to split the code and state which are sent via authorization page. The GetToken method has already been discussed.
#r "System.Web.Extensions"
#load "<file name>.csx" --- this file writes data to file in encrypted format
using System.Net;
using System.Web.Script.Serialization;
using System.Net.Http;
using System.Text;
public static HttpResponseMessage Run(HttpRequestMessage req, TraceWriter log)
{
string code = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "code", true) == 0)
.Value;
string state = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "state", true) == 0)
.Value;
var token = GetToken(code, false, log);
string[] vars = state.Split(':');
JavaScriptSerializer serializer = new JavaScriptSerializer();
var AccessArr = serializer.Deserialize<Dictionary<string, string>>(token);
<method name from other file>(vars[0], AccessArr, log); --- writes data to file
var response = req.CreateResponse(HttpStatusCode.Moved);
response.Headers.Location = new Uri("https://" + vars[0] + ".visualstudio.com/" + vars[1] + "/_apps/hub/<hub path>");
return response;
}
public static string GetToken(string code, bool refresh, TraceWriter log) <already shown>
The complete url for calling authorization call back function and the authorization page, looks as follows
https://app.vssps.visualstudio.com/oauth2/authorize?client_id=&response_type=Assertion&state=&scope=vso.project%20vso.work_write&redirect_uri=https:// &:embed=yes
This url can be used in our HTML file where the call to authorization is needed.
Conclusion
This article discussed how to create and execute call back function from the azure website. We also saw how to create an authorization page for an organization. Once both are ready, we can incorporate it in our VSTS extension. The last piece of functionality we used in our VSTS extension is to create Service hooks.
In the next article, I will discuss about service hooks, how they can be created, and how they can be applied to your VSTS account.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Gouri is a Trainer and Consultant on Azure DevOps and Azure Development. She has an experience of three decades in software training and consulting. She is a graduate from Pune University and PGDCA from Pune University. Gouri is a Microsoft Most Valuable Professional (MVP) - Developer Technologies (Azure DevOps), Microsoft Certified Trainer (MCT) and a Microsoft Certified Azure DevOps Engineer Expert. She has conducted over 150 corporate trainings on various Microsoft technologies. She is a speaker with Pune User Group and has conducted sessions on Azure DevOps, SQL Server Business Intelligence and Mobile Application Development. Gouri has written more than 75 articles on Azure DevOps, TFS, SQL Server Business Intelligence and SQL Azure which are published on
www.sqlservercurry.com and
www.dotnetcurry.com. You can connect with her on
LinkedIn.