Production Environment

Usage

Background

PostNord Strålfors recommends using OpenID/OAuth2 for authentication/authorization when sending and receiving files over the HTTP protocol.

You can read more about OpenID here: https://openid.net/developers/how-connect-works

And more about OAuth2 here: https://oauth.net/2

Most middlewares and commercial products provides support for these protocols and procedures and if you are using an inhouse system it's quite easy to extend it. If you are using a standard OpenID/OAuth2 client it should be enough to configure it to use https://api.stralfors.com/.well-known/openid-configuration

How to obtain a token

For EDI transfers we support the "client credentials flow" which means that you have to first get hold of token that you later submit as an HTTP header for all other requests. Our implementation adheres to the standard practice for discovery of OpenID endpoints so a standard client should be possible to point to: https://api.stralfors.com/.well-known/openid-configuration and then the relevant endpoints is automatically found:

curl -s https://api.stralfors.com/.well-known/openid-configuration | jq
{
  "issuer": "https://api.stralfors.com/v1/oidc",
  "authorization_endpoint": "https://api.stralfors.com/v1/oidc/authorization",
  "token_endpoint": "https://api.stralfors.com/v1/oidc/token",
  "jwks_uri": "https://api.stralfors.com/v1/oidc/jwks",
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "claims_supported": [
    "iss",
    "sub"
  ],
  "grant_types_supported": [
    "client_credentials"
  ]
}

To exchange your credentials for a token just do the following(with USERNAME/PASSWORD replaced by real values of course):

curl -s -X POST -H'Content-type: application/x-www-form-urlencoded' 'https://api.stralfors.com/v1/oidc/token?grant_type=client_credentials&client_id=USERNAME&client_secret=PASSWORD' | jq
{
  "access_token": "eyJraWQiOiJpbGF1dGhraWQiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2ODU2MjI4MjksInN1YiI6Imlkb250ZXhpc3QiLCJpc3MiOiJodHRwczovL3Rlc3QuYXBpLnN0cmFsZm9ycy5jb20vdjEvb2lkYyIsIm5iZiI6MTY4NTYyMjc2OSwianRpIjoiMGU1YjBkMjItYWZjNy00N2FlLTg2NDYtNDc3ZmYyZmYwODE0Iiwic2NvcGUiOlsib3BlbmlkIl0sImluZm9saW5rU3lzdGVtIjpbXSwibWF4VXNlclNlc3Npb25zIjowLCJhdWQiOlsiaHR0cHM6Ly90ZXN0LmFwaS5zdHJhbGZvcnMuY29tIl0sImV4cCI6MTY4NTcwOTIyOX0.eCOIXqOHYj5kpCn0FMziwJ0oVj5tWcqTDN1ESJj7B0kQqbAOynfF36CJNlgrPmL-WzTw-667h9GV7PvFv8NEGS_StPBAcmMsMEjH1wLYsNMBs1LgkLlmTSYzDOTVZxCskk_aqG-ZTOjtgDsCJXQshvjzT2_sPhOg0J9VYeTmEcBYp0xdq93iGmwD9BEBl_duu0BOqqyFEc9hw_ILW7ffA7FM831tkFC58XPpFqqyh_ufSJyDmvEEO3wnPw0Av2xyGC3wDfgUapVZ_cEncfoH8JA2H4EgoQe1yZ1t0NakcytaEH7TUdwBcoIL-U8ybTtjat33UYWberDpxSStvvGV-MoLHtG6s5PkkQWvPDR3as7CctGX_5qcXZTFts0R7sv41cNRxIvbmwwjD9jcLf1c6Hl2x2RUhQNG1S0eQcKVmvuDo59am0ebVD5dq4bdj7lqfpKhH0eQj5JZHdtdpzQl8S6MEwBC-PegJ45ty0nHLvdsWel9nTKikzHEnxJDAsgz1vCnJdbtiXLK9y8KNikIgUFl-afCWywt9UqjqRq809wXjEKQzePLMkcQLh0_G6A5TO8L9nnnUsgbGz0-qONbfzUdR1W4_tBp7oTlz7yig2qXvXf7DmwyU9fXqGkMc6nk_uoYnzKFkQj_iSTNu2xinRhFHnooj8sQEosYgGF-vOs",
  "token_type": "Bearer",
  "expires_in": 86400
}

The received JSON object contains an access token but also an expiry date which is the number of seconds that the token is valid before a request for a new token is needed.

How to use the token

When submitting or receiving files a valid token is required and shall be submitted using an HTTP header called "Authorization" with FILE, FILENAME and JWT replaced by real values:

Submitting a file currently accepts content type multipart/form-data and text/xml.

Post a file using multipart/form-data:

curl -s --form file='@FILE -H "Authorization: Bearer JWT" https://api.stralfors.com/rest/bim/http/v1/staticroute

Post a file using text/xml

curl -s -X POST -d '@FILE' -H "fileName: FILENAME" -H "Content-Type: text/xml" -H "Authorization: Bearer JWT" https://api.stralfors.com/rest/bim/http/v1/staticroute

Detailed example of both token request and posting a file via our HTTP API

myfile="/tmp/invoice.xml"
jwt=$(curl -s -X POST -H'Content-type: application/x-www-form-urlencoded' 'https://api.stralfors.com/v1/oidc/token?grant_type=client_credentials&client_id=USERNAME&client_secret=PASSWORD' | jq .access_token | tr -d '"')
curl -s --form file="@$myfile" -H "Authorization: Bearer $jwt"  https://api.stralfors.com/rest/bim/http/v1/staticroute

Client Examples

Below are examples for obtaining a token and posting a file using different programming languages.

Java (Spring Boot)

import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.core.io.FileSystemResource;
import java.io.File;
import java.util.Map;

public class StralforsClient {

    private static final String TOKEN_URL = "https://api.stralfors.com/v1/oidc/token";
    private static final String UPLOAD_URL = "https://api.stralfors.com/rest/bim/http/v1/staticroute";

    private final RestTemplate restTemplate = new RestTemplate();
    private final String clientId;
    private final String clientSecret;

    public StralforsClient(String clientId, String clientSecret) {
        this.clientId = clientId;
        this.clientSecret = clientSecret;
    }

    public String getAccessToken() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "client_credentials");
        body.add("client_id", clientId);
        body.add("client_secret", clientSecret);

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
        ResponseEntity<Map> response = restTemplate.postForEntity(TOKEN_URL, request, Map.class);

        return (String) response.getBody().get("access_token");
    }

    public void uploadFile(String accessToken, File file) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        headers.setBearerAuth(accessToken);

        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", new FileSystemResource(file));

        HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
        restTemplate.postForEntity(UPLOAD_URL, request, String.class);
    }

    public static void main(String[] args) {
        StralforsClient client = new StralforsClient("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
        String token = client.getAccessToken();
        client.uploadFile(token, new File("/tmp/invoice.xml"));
    }
}

.NET / C#

using System.Net.Http.Headers;

public class StralforsClient
{
    private const string TokenUrl = "https://api.stralfors.com/v1/oidc/token";
    private const string UploadUrl = "https://api.stralfors.com/rest/bim/http/v1/staticroute";

    private readonly HttpClient _httpClient = new HttpClient();
    private readonly string _clientId;
    private readonly string _clientSecret;

    public StralforsClient(string clientId, string clientSecret)
    {
        _clientId = clientId;
        _clientSecret = clientSecret;
    }

    public async Task<string> GetAccessTokenAsync()
    {
        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "client_credentials"),
            new KeyValuePair<string, string>("client_id", _clientId),
            new KeyValuePair<string, string>("client_secret", _clientSecret)
        });

        var response = await _httpClient.PostAsync(TokenUrl, content);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadFromJsonAsync<JsonElement>();
        return json.GetProperty("access_token").GetString()!;
    }

    public async Task UploadFileAsync(string accessToken, string filePath)
    {
        using var form = new MultipartFormDataContent();
        using var fileStream = File.OpenRead(filePath);
        using var fileContent = new StreamContent(fileStream);

        fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        form.Add(fileContent, "file", Path.GetFileName(filePath));

        _httpClient.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", accessToken);

        var response = await _httpClient.PostAsync(UploadUrl, form);
        response.EnsureSuccessStatusCode();
    }

    public static async Task Main(string[] args)
    {
        var client = new StralforsClient("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
        var token = await client.GetAccessTokenAsync();
        await client.UploadFileAsync(token, "/tmp/invoice.xml");
    }
}

JavaScript (Node.js)

const fs = require('fs');
const path = require('path');
const FormData = require('form-data');

const TOKEN_URL = 'https://api.stralfors.com/v1/oidc/token';
const UPLOAD_URL = 'https://api.stralfors.com/rest/bim/http/v1/staticroute';

async function getAccessToken(clientId, clientSecret) {
    const params = new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: clientId,
        client_secret: clientSecret
    });

    const response = await fetch(TOKEN_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: params
    });

    if (!response.ok) {
        throw new Error(`Token request failed: ${response.status}`);
    }

    const data = await response.json();
    return data.access_token;
}

async function uploadFile(accessToken, filePath) {
    const form = new FormData();
    form.append('file', fs.createReadStream(filePath), path.basename(filePath));

    const response = await fetch(UPLOAD_URL, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            ...form.getHeaders()
        },
        body: form
    });

    if (!response.ok) {
        throw new Error(`Upload failed: ${response.status}`);
    }

    return response;
}

// Usage
(async () => {
    const clientId = 'YOUR_CLIENT_ID';
    const clientSecret = 'YOUR_CLIENT_SECRET';

    const token = await getAccessToken(clientId, clientSecret);
    console.log('Token obtained successfully');

    await uploadFile(token, '/tmp/invoice.xml');
    console.log('File uploaded successfully');
})();