API Sample Code
Download Link
Download the sample project here
Walkthrough of the Sample app
Setting up the project
As I started from scratch I began by creating a new ASP.NET Web Application project in Visual Studio, and for the sake of simplicity I chose the Web Forms template. I will also use the popular third-party library DotNetOpenAuth in this example.
As I used a template to create my project, it has some default files and content I do not wish to use. I deleted those files I would not use and cleaned out the default.aspx from the default content. In the Site.Master file, I also deleted the navigation menu and the footer so I got a somewhat clean state from which to continue.
Adding DotNetOpenAuth by NuGet to the project is easily done by opening the Package Manager Console in Visual Studio and running the following command:
PM> Install-Package DotNetOpenAuth
Early on I decided to use web.config for keeping my authorization server configurations, the client id, the secret, redirect uri and the addresses for the authorization server and the api server. Using web.config transformations it will be easy to set up different configurations for the Visma eAccounting sandbox and for the production system.
<appSettings>
<add key="ClientId" value="{INSERT YOUR CLIENT ID HERE}"/>
<add key="ClientSecret" value="{INSERT YOUR CLIENT SECRET HERE}"/>
<add key="RedirectUri" value="https://localhost:44300/callback"/>
<add key="AuthServerAddress" value="https://identity-sandbox.test.vismaonline.com"/>
<add key="ApiServerAddress" value="https://eaccountingapi-sandbox.test.vismaonline.com"/>
</appSettings>
Before I continue with the actual implementation of the authorization process, I enable SSL on the project. I also go into project properties and set the project and start URLs to https://localhost:44300.
Initiating the authorization process
In this example I wished the authorization flow to be triggered by a user interacting with the site. I add a button on the default page so my default.aspx looks like this:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="HowToAuthWebForms._Default" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<p>Click on the button to initiate the Visma eAccounting API authorization process</p>
<asp:Button ID="StartAuthorization" runat="server" Text="Button" OnClick="StartAuthorizationClicked"/>
</asp:Content>
To the code-behind for the Default web form I add:
public partial class _Default : Page
{
...
protected void StartAuthorizationClicked(object sender, EventArgs e)
{
// Here we will put code to start the authorization process
}
}
Before implementing the click handler, I create a new class that I give the name AuthHelper
:
using System;
using System.Web.Configuration;
using DotNetOpenAuth.OAuth2;
namespace HowToAuthWebForms
{
public class AuthHelper
{
public static WebServerClient CreateClient()
{
var clientId = WebConfigurationManager.AppSettings["ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["ClientSecret"];
var serverDesc = GetAuthorizationServerDescription();
var client = new WebServerClient(serverDesc, clientId, clientSecret);
return client;
}
private static AuthorizationServerDescription GetAuthorizationServerDescription()
{
var authServerHost = WebConfigurationManager.AppSettings["AuthServerHost"];
var authorizationEndpoint = String.Format(@"{0}/connect/authorize", authServerHost);
var tokenEndpoint = String.Format(@"{0}/connect/token", authServerHost);
var serverDesc = new AuthorizationServerDescription();
serverDesc.ProtocolVersion = ProtocolVersion.V20;
serverDesc.AuthorizationEndpoint = new Uri(authorizationEndpoint);
serverDesc.TokenEndpoint = new Uri(tokenEndpoint);
return serverDesc;
}
}
}
Using WebConfigurationManager
I can easily access the configuration values I stored in the Web.config file. Now when I have a helper class that will encapsulate the creation of an instance of the DotNetOpenAuth class WebServerClient
, I can go on with the click event handler.
public partial class _Default : Page
{
protected void StartAuthorizationClicked(object sender, EventArgs e)
{
var scopes = new List<string> { "offline_access", "ea:api", "ea:sales" };
var redirectUri = new Uri(WebConfigurationManager.AppSettings["RedirectUri"]);
var client = AuthHelper.CreateClient();
client.RequestUserAuthorization(scopes, redirectUri);
}
}
I create a list containing the scope I need for this application. Calling AuthHelper.CreateClient()
will create and initialize a WebServerClient instance for me. We are now ready to initiate the authorization by calling the RequestUserAuthorization
method, passing in our list of scopes and the redirect URI.
Handling the redirection
Having implemented the click event handler as above, if the web application is started and browsed to it, would present a page with a button. If a user clicks the button it would bring them to the Visma Authorization Server prompting them to sign in with their Visma credentials. When signed in, they would be taken to the consent page where they could allow the application to access their data in Visma eAccounting on their behalf. They could also deny the application access. The application must be able to handle both cases. When a user has made their decision and clicks one of the Allow or Deny buttons, the authorization server will redirect the user’s browser to your application’s redirect URI. To make it possible for the application to determine the user’s choice, the authorization server has appended some values to the redirect uri as query strings that the application can get.
To handle the redirect I add a new web form to my application, giving it the name Callback. If I would now run the application and proceed through the authorization process by signing in with my account and allow the application access, my browser would be redirected to something like this:
https://localhost:44300/callback?code=d74e2128r5424d12b4a7dazd3a0d84e0&state=miGZTYr6U1J6p8-N7rFsNQ
Here we can see that the authorization server has appended extra data to the redirection URI. The code we have now received is to be used for requesting an access token from the authorization server.
One important thing to know about redirect URIs is that Visma Authorization Server requires all redirects to be over SSL, hence the use of the https scheme.
Requesting an access token
I add another helper method method to the AuthHelper
class, named RequestAuthorization
, that will return an object representing the authorization state. The WebServerClient.ProcessUserAuthorization
method will parse the request url and extract the authorization code and use it to request an access code from the authorization server. ProcessUserAuthorization
will return an instance of AuthorizationState when it completes that will be passed back to the caller of AuthHelper.RequestAuthorization
.
public static IAuthorizationState RequestAuthorization(WebServerClient client)
{
var state = client.ProcessUserAuthorization();
if (state.AccessTokenExpirationUtc <= DateTime.Now.ToUniversalTime())
{
var success = client.RefreshAuthorization(state);
// TODO: if success is false, we need to handle it
}
return state;
}
I change the Page_Load
method of the Callback web form so it looks like this:
protected void Page_Load(object sender, EventArgs e)
{
var client = AuthHelper.CreateClient();
var state = AuthHelper.RequestAuthorization(client);
Response.Write(state.AccessToken);
}
Once again I use the AuthHelper
class to help me set up an instance of WebServerClient
that is assigned to the local variable client. The object in client is passed along to the AuthHelper.RequestAuthorization
method that will return an instance of the class AuthorizationState
. Object instances of this class have a property named AccessToken
, a string containing the access token used for accessing protected resources in the Visma eAccounting API. If a user deny the application access, the values in the state
variable will be uninitialized. If that is the the case, the redirection URI query string will contain more info for us. I update the Page_Load
method to check for authorization errors and output them to the page if any. Otherwise it will respond with the access token.
protected void Page_Load(object sender, EventArgs e)
{
var client = AuthHelper.CreateClient();
var state = AuthHelper.RequestAuthorization(client);
if (String.IsNullOrEmpty(state.AccessToken))
{
var error = HttpUtility.ParseQueryString(Request.Url.Query.Substring(1))["error"];
if (error == "access_denied")
{
Response.Write("The user denied access, aborting authorization...");
}
else
{
Response.Write("An unexpected error has occurred: " + error);
}
return;
}
Response.Write(state.AccessToken);
}
Accessing the API
At this point we have an application that is able to acquire user authorization. If we have come this far with our sample application, we cannot stop here. We must try it out. I add another method to the AuthHelper
class:
public static T GetProtectedResource<T>(WebServerClient client, IAuthorizationState state, string path)
{
var apiServerHost = WebConfigurationManager.AppSettings["ApiServerHost"];
var request = (HttpWebRequest)WebRequest.Create(new Uri(String.Format("{0}{1}", apiServerHost, path)));
request.Method = "GET";
request.ContentType = "application/json";
client.AuthorizeRequest(request, state);
var webResponse = request.GetResponse();
var response = new StreamReader(webResponse.GetResponseStream()).ReadToEnd();
var jss = new JavaScriptSerializer();
var result = jss.Deserialize<T>(response);
return result;
}
his method is a convenient helper that will perform a web request (GET) using HttpWebRequest to protected resources in the Visma eAccounting API. The method is generic and will attempt to deserialize the response to objects of type T before returning the result. I leave it to the reader to make it error proof.
To be able to serialize the resulting javascript from the /v2/articles
resource, I first need to create three new classes:
public class ArticleResponse
{
public Meta Meta { get; set; }
public List<Data> Data { get; set; }
}
public class Meta
{
public int CurrentPage { get; set; }
public int PageSize { get; set; }
public int TotalNumberOfPages { get; set; }
public int TotalNumberOfResults { get; set; }
public DateTime ServerTimeUtc { get; set; }
}
public class Data
{
public Guid Id { get; set; }
public bool IsActive { get; set; }
public string Number { get; set; }
public string Name { get; set; }
public string NameEnglish { get; set; }
public decimal NetPrice { get; set; }
public decimal GrossPrice { get; set; }
public Guid CodingId { get; set; }
public Guid UnitId { get; set; }
public string UnitName { get; set; }
public decimal StockBalance { get; set; }
public decimal StockBalanceReserved { get; set; }
public decimal StockBalanceAvailable { get; set; }
public DateTime ChangedUtc { get; set; }
}
I update Page_Load
so instead of responding with the access token it will now make an API call.
...
else
{
Response.Write("An unexpected error has occurred: " + error);
}
return;
}
var articles = AuthHelper.GetProtectedResource<ArticleResponse>(client, state, "/v2/articles");
Response.Write("Number of articles: " + articles.Data.Count);
}
...
If I now start the application and browse to it, the response after authorizing the application would be a text saying how many articles was fetched from the API. Success!
Updated 7 months ago