Connect with Visual Studio Online from your Universal App

Last year I published an application called Product Manager. This application supports the product owner in scrum team to work with Visual Studio Online. It offers functionality to create new product backlog items, manage the backlog, review ideas and break down product backlog item in smaller, more detailed items. To build this app I used the TFS OData Services (tfsodata.visualstudio.com/). This offered OData service to some data of Visual Studio online. Unfortunately it wasn’t very completed with sometimes resulted in a bad user experience.


Microsoft launched now an official REST Api to Visual Studio Online. This API offers the complete functionality of Visual Studio Online through the rest services. You can find more information about it here: http://www.visualstudio.com/integrate/get-started/get-started-rest-basics-vsi
In this blogpost I will show you how can authenticate against Visual Studio Online using OAuth. Visual Studio offers also basic authentication. But this requires the end user enabled alternate authentication credentials which blocks an out of the box working experience for your app.

Step 1, Register your app

Before you can use OAuth you need to register your application. You can do that here: https://app.vssps.visualstudio.com/app/register . You will need to provide the name of your application and description and return url. After you complete this form you get a appid, app secret and return url. You need to copy these values to your app.
I always put these values as consts in my class

private const string APP_ID = "";

private const string APP_SECRET = "";

private const string RETURN_URL = "";

Step 2, Authorize

First you need to user authorize with Visual Studio Online. You can do this at this url: https://app.vssps.visualstudio.com/oauth2/authorize and provide a couple of parameters. This will show a login form where the user can login and after login the form will go to your return url with as parameter the authentication code. For this scenario we have the WebAuthenticationBroker in universal apps.
The parameters you need to send:

  • client app / appid
  • responsetype; this always should be assertion
  • state: this you can fill in what you want. This state will come back with the result
  • scope: the required permission your application need. At the moment there is only one scope you have to provide to get full access: preview_api_all Preview_msdn_licensing
  • return uri: url where the form returns to after it been completed

In code:

const string urlMask_Authorize = @"https://app.vssps.visualstudio.com/oauth2/authorize?client_id={0}&response_type=Assertion&state=vso&scope=preview_api_all%20preview_msdn_licensing&redirect_uri={1}";
string url_Authorize = string.Format(urlMask_Authorize, APP_ID, RETURN_URL);
var authorizeResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(url_Authorize), new Uri(RETURN_URL));

After login the WebAuthtenticationBroker returns the result. We need to check if its completed and get the authentication code out of it

switch (authorizeResult.ResponseStatus)
{
    case WebAuthenticationStatus.Success:
 
        int splitIndex = authorizeResult.ResponseData.IndexOf("=");
        int lastIndex = authorizeResult.ResponseData.LastIndexOf("&") - 1;
        string code = authorizeResult.ResponseData.Substring(splitIndex + 1, lastIndex - splitIndex);

}

Step 3, get the access_token

We need to get an access token to communicate with the rest api. The authentication code isn’t good for this. So we need to exchange this authentication code for an access_code. That can be done with a post to this url: https://app.vssps.visualstudio.com/oauth2/token
Also we need to provide some variables again

  • client_assertion_type: always need to be urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • client_assertion: need to be the app secret you obtained during the registration of your app
  • grant_type: always need to be urn:ietf:params:oauth:grant-type:jwt-bearer
  • assertion: the authorization code you obtained with the WebAuthenticationBroker
  • redirect_uri: your return url

You can do this request with the HttpClient. All post parameters need to be form url encoded

const string postDataMask = "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}";
string postData = string.Format(postDataMask, APP_SECRET, code, RETURN_URL);
 
var postvaluesAccessToken = new List<KeyValuePair<stringstring>>();
postvaluesAccessToken.Add(new KeyValuePair<stringstring>("client_assertion_type""urn:ietf:params:oauth:client-assertion-type:jwt-bearer"));
postvaluesAccessToken.Add(new KeyValuePair<stringstring>("client_assertion"string.Format("{0}", APP_SECRET)));
postvaluesAccessToken.Add(new KeyValuePair<stringstring>("grant_type""urn:ietf:params:oauth:grant-type:jwt-bearer"));
postvaluesAccessToken.Add(new KeyValuePair<stringstring>("assertion"string.Format("{0}", code)));
postvaluesAccessToken.Add(new KeyValuePair<stringstring>("redirect_uri"string.Format("{0}", RETURN_URL)));
 
HttpRequestMessage messageAccessToken = new HttpRequestMessage(HttpMethod.Post, new Uri(ACCESS_TOKEN_URL));
messageAccessToken.Content = new HttpFormUrlEncodedContent(postvaluesAccessToken);
 
HttpClient client1 = new HttpClient();
var accessTokenResult = await client1.SendRequestAsync(messageAccessToken);


This request will return some json with there the access token. The json will look like this

{"access_token":"<access_token>","token_type":"jwt-bearer","expires_in":"899","refresh_token":"<refreshtoken>","scope":"preview_api_all preview_msdn_licensing"}

With Json.Net you can easily transform the json into an object. Define this class:

public class AccessTokenResult
{
    public string access_token { getset; }
    public string token_type { getset; }
    public string expires_in { getset; }
    public string refresh_token { getset; }
    public string scope { getset; }
}

 

And then transform the response into an instance of this class:

string accessTokenJson = await accessTokenResult.Content.ReadAsStringAsync();
var accessToken = JsonConvert.DeserializeObject<AccessTokenResult>(accessTokenJson);

Step 4, test your code

Best to see if everything is working fine is doing an rest request. I always pick the one for the builds. That’s an easy http get request to: https://smitstfs.visualstudio.com/DefaultCollection/_apis/build/builds
Before doing the request you need to set the access_token as http header.

client1.DefaultRequestHeaders.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("bearer", accessToken.access_token);
string jsonResult = await client1.GetStringAsync(new Uri("https://smitstfs.visualstudio.com/DefaultCollection/_apis/build/builds"));

If this request completes and you get the json result with build information you are ready to use the api’s! Only the access_token isn’t valid forever and to avoid the user have to login each time the access token also comes with a refresh token. So it’s important you also store this refresh_token

Step 5 get new access_token with a refresh token

This refresh token can be used to get a new access token. Getting a new access token with a refresh token is almost the same as obtaining an access code with an authentication code. You use the same url and almost the same parameters. Things that change:

  • grant_type parameter was urn:ietf:params:oauth:grant-type:jwt-bearer and now becomes refresh_token
  • assertion was the authorization code, that now becomes the refresh code

The result of this call would be exactly the same as you had when you did the request with an authorization code. You get a new access_token and a new refresh token. Save them both so you can easily renew again. Code for this

var postvaluesRefresh = new List<KeyValuePair<stringstring>>();
postvaluesRefresh.Add(new KeyValuePair<stringstring>("client_assertion_type""urn:ietf:params:oauth:client-assertion-type:jwt-bearer"));
postvaluesRefresh.Add(new KeyValuePair<stringstring>("client_assertion"string.Format("{0}", APP_SECRET)));
postvaluesRefresh.Add(new KeyValuePair<stringstring>("grant_type""refresh_token"));
postvaluesRefresh.Add(new KeyValuePair<stringstring>("assertion"string.Format("{0}", accessToken.refresh_token)));
postvaluesRefresh.Add(new KeyValuePair<stringstring>("redirect_uri"string.Format("{0}", RETURN_URL)));
 
HttpRequestMessage messageRefreshToken = new HttpRequestMessage(HttpMethod.Post, new Uri(ACCESS_TOKEN_URL));
messageRefreshToken.Content = new HttpFormUrlEncodedContent(postvaluesRefresh);
 
HttpClient client2 = new HttpClient();
var refreshTokenResult = await client2.SendRequestAsync(messageRefreshToken);
string refreshTokenJson = await refreshTokenResult.Content.ReadAsStringAsync();
var refreshToken = JsonConvert.DeserializeObject<AccessTokenResult>(accessTokenJson);

Now you are really ready with your authentication against Visual Studio Online and can start using the api’s to add your custom functionality for Visual Studio Online!

Soon I will do another blog post about more things that are possible with these rest api’s

  •   RT @windowsblog: Windows Template Studio 1.1 released! https://t.co/qCLpESAvnB
  •   @JenMsft and does sqrt(4) - 2 gives now the right awnser?
  •   @Alex_A_Simons any updates on that what more is happening ? (2/2)
  •   @Alex_A_Simons long time ago you said the mess between msa and aad's would be fixed; first step was block to create msa on aad account. (1/2
  •   First pullrequest for https://t.co/5lEHH630Io :)
  •   @qmatteoq whosdown is great alternative for whatapps and comes from the store
  •   @jvintzel only for windows 10 s users or can other surface users get it too?
  •   RT @JenMsft: O hi there @Spotify 😊 #Windows10 https://t.co/nlJUgM7vVG https://t.co/boah802WgZ
  •   RT @tvgrimbergen: Awesome! Just extended our @MicrosoftTeams bot. Provide natural language input to initiate a leave request powered by @Ni…
  •   @lancewmccarthy kind of sad :/