API

ID verification requests

After a successful login to the RamBase API an access token is generated and returned to the user. This token is used in all subsequent requests to ensure valid authentication. If anyone gets hold of this access token it can be used to impersonate the user, either by retrieving data or performing operations on behalf of the user. This is the reason why access tokens never should be shared with anyone!

In some cases, a third-party integration or system needs to know if there exists a valid session for a user in RamBase. Since access tokens should never be distributed, an ID verification token exists for this purpose. This token can be used to find out who the user is in RamBase, and which RamBase system they are signed in to.

Any user can retrieve an ID verification token by making a GET request to the /id-verification-token endpoint. Note that this endpoint requires a valid access token.

GET https://api.rambase.net/id-verification-token

The returned ID verification token is a JWT (JSON Web Token) and can safely be sent to anyone!

The JWT contains information about who the user is and which RamBase system the user is logged in to. If logged into supplier- or customer portal, the id of the supplier/customer is also provided.

Example usage of ID verification token

A RamBase development partner has developed an AI engine that can predict sales based on the first name of a customer. They decide to create an extension in RamBase to make this available for all RamBase users. They want to keep the algorithm safe and decide to create a REST service called ForecastRESTAPI which they develop and maintain themself, outside of the RamBase environment. They do not want to spend time on handling user management and making a secure authentication, which makes ID verification tokens a perfect match!

This is how the extension works from the RamBase side:

  1. A REX component is created with an input box for entering the first name and a submit button.
  2. When the user clicks the submit button the REX component makes an API request to https://api.rambase.net/id-verification-token with the user's access token to get an ID verification token.
  3. The first name from the text input and the ID verification token are sent from the REX component to the ForecastRESTAPI. The users access token is never sent.
  4. The REX component receives the response from the ForecastRESTAPI and presents the list of forecasted sales orders.

This is how the extension works from the ForecastRESTAPI side:

  1. The only endpoint of the API requires a first name. Since the API is only intended for RamBase users they also require an ID verification token. Also, they would like to know and keep track of which users and systems the requests are coming from.
  2. When the ForecastRESTAPI receives the request, they need to verify the ID verification token. To do that they need to decode and validate the signature part of the JWT as explained above. If the token has not expired and the public key verifies the token, they know that the ID verification token belongs to an authenticated RamBase user. No additional user handling or security is needed.
  3. Information about the requester is logged.
  4. The forecasted sales orders are generated and returned.

Note that access tokens created for RamBase API is never sent anywhere else than to the RamBase API! The ID verification token on the other hand can be shared with anyone.

Structure of the response

JWT is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. Because the JWT is signed using public/private key pairs you can be sure that the RamBase API is the sender. Additionally, as the signature is calculated using the header and the payload, you can (and should) also verify that the content hasn't been tampered with. Read more about JWTs at jwt.io.

The structure of the response is divided into three parts, separated with dots:
base64url(header) . base64url(payload) . base64url(signature)

Below follows a short description of these three parts after being decoded.

1. Header

The header consists of three properties:

  • "alg": Type of algorithm. Always "RS256" in our case.
  • "kid": Reference to the public key. More about the RamBase API public key in the signature section.
  • "typ": Type of web token. Always "JWT" in our case.

Example header:

{
    "alg": "RS256",
    "kid": "MIICCgKCAgEAr5gEOuflJjxY3BnS6J",
    "typ": "JWT"
}

2. Payload

The payload contains information which can be used to identify a valid RamBase user session.

  • "sub": The subject. The RamBase PID associated with the user session.
  • "systemName": The name of the RamBase system associated with the user session.
  • "exp": Expiration time as seconds since Unix epoch. Our ID verification tokens only have a lifetime of five minutes.
  • "iss": The issuer should always be "http://api.rambase.net".
  • "aud": The audience. The API client requesting the token.
  • "customerId": The customer associated with the user session.
  • "supplierId": The supplier associated with the user session.

Example payload:

{
    "sub": "1234",
    "systemName": "HATTELAND",
    "exp": 1666726246,
    "iss": "https://api.rambase.net",
    "aud": "U5dLKPL29U6gUKE03JXXSSD",
    "customerId": 1234,
    "supplierId": 1234
}

3. Signature

The signature is used to verify the message wasn't changed along the way, and, since we are signing tokens with a private key, it can also verify that the sender of the JWT is who it says it is. Since the signature is signed with our private key, you will need our public key to be able to verify the signature. We provide our public keys through a json web key set at the following endpoint:

GET https://api.rambase.net/oauth2/jwks

Example response:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "MIICCgKCAgEAr5gEOuflJjxY3BnS6J",
      "e": "AQAB",
      "n": "r5gEOuflJjxY3BnS6J3tZwQi9gmUIbATPC5H57y60j60qkWlvi2LolxGZy90S1W3GvX7vcZPAv1fmWxMM..."
    }
  ]
}

Note that the identifier of the key ("kid") should be the same as used in the header of the JWT token.

Our public keys will not change often, which means that applications should cache these keys. On the other side it is important to handle key rotations. Applications developers will need to decide on a cache lifetime that strikes a good balance between performance and security. We recommend the following:

  • Fetch new JWKs e.g., every few hours
  • Fetch new JWKs if a "kid" reference in a token can't be found in the cached JWKs.
  • Wait at least 5 minutes before trying to fetch new JWKs. Someone could try a DDOS attack by sending a token with a non-existing "kid".

Example of signature validation in C#

Below follows an example of some C# code validating the ID verification token.

using IdentityModel;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

namespace ConsoleAppExample
{
    internal static class JwtValidator
    {
        public static async Task ValidateIdVerificationToken(string jwt)
        {
            var client = new HttpClient();
            client.BaseAddress = new Uri("https://api.rambase.net");

            // The keys should be cached to avoid requesting the key set on every validation
            var res = await client.GetAsync("/oauth2/jwks");
            var keyset = new JsonWebKeySet(await res.Content.ReadAsStringAsync());
            var keys = new List();
            foreach (var webKey in keyset.Keys)
            {
                var e = Base64Url.Decode(webKey.E);
                var n = Base64Url.Decode(webKey.N);
                var key = new RsaSecurityKey(new RSAParameters { Exponent = e, Modulus = n })
                {
                    KeyId = webKey.Kid
                };
                keys.Add(key);
            }
            var parameters = new TokenValidationParameters
            {
                ValidIssuer = "https://api.rambase.net",
                ValidateAudience = false,
                ValidateLifetime = true,
                IssuerSigningKeys = keys,
                RequireSignedTokens = true,
                ValidateIssuerSigningKey = true,
                TryAllIssuerSigningKeys = false
            };
            var handler = new JwtSecurityTokenHandler();
            return handler.ValidateToken(jwt, parameters, out var _);
        }
    }
}