Unimicro Web Auth
Introduction
In this article you will see how we can set up an integration against Unimicro that will work for multiple platforms.
Prerequisites
- A Unimicro account
- Access to a company in Unimicro
- Client ID
- Client Secret
Create your app first.
If you already have an app, you can manage it from Applications.
Tools
In the coding examples we'll be using C# in .net core 3.1. All dependencies will be listed in Appendix A and B.
Notes
In this article <client_id> and <client_secret> represents the client id and client secret that belongs to your client.
Getting an access token
In this section we will show how to retrieve an access token.
Step 1
Send the user to:
https://test-login.unimicro.no/connect/authorize?client_id=<client-id>&redirect_uri=<redirect-uri>&response_type=code&prompt=login&scope=AppFramework profile openid offline_access&state='<optional-state>'
The client-id is the client Id you got for your app, the redirect-url is one of the uri you have registered, and the scopes is should be "AppFramework profile openid offline_access".
The user will now login, chose a platform and a company, and then are redirected back to you with a code that you will need to be able to get the access token. The redirect_uri must be registered on the client, so if you have dynamic redirect internally after authentication, you can specify this in the state parameter. This will return with the same value, after a successful authentication.
Step 2
Use the code that you got back in step 2 and create a POST request against our identity provider.
POST https://test-login.unimicro.no/connect/token
Headers:
"Content-Type":"application/x-www-form-urlencoded"
Body:
"grant_type": "authorization_code",
"code": CODE_FROM_REDIRECT,
"redirect_uri": https://integration-partner/post-login,
"client_id": <client_id>,
"client_secret": <secret>
Example in C#
var httpClient = new HttpClient();
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "<client_id>"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_secret", "<client_id>"),
new KeyValuePair<string, string>("redirect_uri", "https://integration-partner/post-login")
};
var content = new FormUrlEncodedContent(pairs);
var result = await httpClient.PostAsync(new Uri("https://test-login.unimicro.no/connect/token"), content)
You'll then get a JSON response that looks like this:
{
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Im...",
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImVhMD...",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "8942647edb873533c35a..."
}
Step 2b - Use refresh token to get a new access token
The process is similar to the above. The difference is that we will change the grant_type to "refresh_token" and replaces code with refresh_token.
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "<client_id>"),
new KeyValuePair<string, string>("refresh_token", identityResponse.refresh_token),
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("client_secret", "<client_id>"),
new KeyValuePair<string, string>("redirect_uri", "https://integration-partner/post-login")
};
Step 3 - Talk with the Unimicro API
Now you have an access token and are ready to contact the API. If you have a single tenant client and the user has selected a company you do not need to specify a CompanyKey in the headers, but if you have a multi-tenant client, you must specify the CompanyKey in the headers.
The first thing to do is to obtain the URL for our API from the token. The URL we are looking for is stored on a claim in the token called AppFramework
var jwtSecurityToken = new JwtSecurityToken(accessToken);
jwtSecurityToken.Payload.TryGetValue("AppFramework", out var baseUrl);
It could be smart to wrap this in a try-catch.
Single tenant request:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var apiResult = await httpClient.GetAsync(new Uri(baseUrl + "api/biz/user?action=current-session"));
Multiple tenant request:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
httpClient.DefaultRequestHeaders.Add("CompanyKey", "015eb513-753a-4942-9f6a-8ba930e33dc6");
var apiResult = await httpClient.GetAsync(new Uri(baseUrl + "api/biz/user?action=current-session"));
Headers for multi tenant should include:
Content-Type: application/json
Authorization: Bearer [JWT]
CompanyKey: [CompanyKey-from step 3b]
In both cases your response should look something like:
{
"Permissions": [],
"License": {},
"DisplayName": "My Name",
"Email": "dev@unimicro.no",
"GlobalIdentity": "ee5a6e9f-9ca8-4c97-9bf0-6563633d594f",
"PhoneNumber": "",
"LastLogin": "2019-02-12T12:15:23.85Z",
"UserName": "Username",
"Protected": false,
"BankIntegrationUserName": null,
"IsAutobankAdmin": false,
"StatusCode": null,
"CustomValues": null,
"ID": 1,
"Deleted": false,
"CreatedAt": null,
"UpdatedAt": null,
"CreatedBy": null,
"UpdatedBy": null
}
Step 3b - Multi tenant company key
To get the companies that is available for the current session, you'll make a request against http://test.unimicro.no/api/init/companies
with the Authorization header set with Bearer [JWT]. This will provide you with a return that looks something like:
{
"Name":"Company Inc.",
"Key":"015eb513-753a-4942-9f6a-8ba930e33dc6",
"WebHookSubscriberId":null,
"IsTest":false,
"FileFlowEmail":null,
"ID":5,
"Deleted":false,
"CreatedAt":"2017-02-01T15:55:43.46Z",
"UpdatedAt":null,
"CreatedBy":null,
"UpdatedBy":null
}
You will then use the Key from the response as CompanyKey for the company you wish to make the request against. You can switch which company you make requests against, by switching the CompanyKey header.
Appendices
Appendix A - NuGet packages
Microsoft.AspNetCore.App
Microsoft.NETCore.App
Newtonsoft.Json
System.IdentityModel.Tokens.Jwt
Appendix B - Usings
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
Appendix C - Code example
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace MyApp.Controller
{
Route("api/[controller]")]
[Produces("application/json")]
[ApiController]
public class TokensController : ControllerBase
{
private readonly Client Client = new Client
{
clientId = "<client_id>",
clientSecret = "<client_secret>"
};
[HttpGet]
public async Task<IActionResult> GetToken([FromQuery(Name = "code")] string code = null)
{
if (code == null)
{
return Ok("No code");
}
var httpClient = new HttpClient();
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", Client.clientId),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_secret", Client.clientSecret),
new KeyValuePair<string, string>("redirect_uri", "https://yourdomain.no/redirects-here"),
};
var content = new FormUrlEncodedContent(pairs);
var result = await httpClient.PostAsync(new Uri("https://test-login.softrig.com/connect/token"), content);
var returnContent = await result.Content.ReadAsStringAsync();
if (!result.IsSuccessStatusCode) return BadRequest(returnContent);
var identityResponse = JsonConvert.DeserializeObject<IdentityResponse>(returnContent);
return await GetDataFromCompany(identityResponse.access_token);
}
private async Task<IActionResult> GetDataFromCompany(string accessToken)
{
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
try
{
var jwtSecurityToken = new JwtSecurityToken(accessToken);
if (jwtSecurityToken.Payload.TryGetValue("AppFramework", out var baseUrl))
{
Console.WriteLine($"Base url is {baseUrl}");
var apiResult = await httpClient.GetAsync(new Uri(baseUrl + "api/biz/companysettings"));
return Ok(await apiResult.Content.ReadAsStringAsync());
}
else
{
return BadRequest($"Could not find claim on token");
}
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
return BadRequest("this is wrong");
}
}
internal class IdentityResponse
{
public string id_token { get; set; }
public string access_token { get; set; }
public int expires_in { get; set; }
public string token_type { get; set; }
public string refresh_token { get; set; }
}
internal class Client
{
public string clientId { get; set; }
public string clientSecret { get; set; }
}
}