Validating security tokens
There are a few of scenarios when applications must perform JSON Web Token (JWT) validation.
When the Authorization Code flow is complete.
When an application obtains a system user ticket from the partner system user service endpoint.
When one of the outbound services begin an invocation cycle. Outbound services include Database Mirroring, ERPSync and Quote Connector.
Token validation establishes trust by the authentication mechanism. It ensures that:
- The token was issued by SuperOffice
- The token was issued to this user
- The user has granted the application access to the listed operation
What is a JWT anyway?
JWT is short for JSON web token:
A string representing a set of claims as a JSON object that is encoded in a JWS or JWE, enabling the claims to be digitally signed or MACed and/or encrypted. (RFC7519)
A JWT has 3 parts: header, payload, signature.
JWT header
The header will show that the token type is JWT and which algorithm that has been used to sign it.
{
"typ":"JWT",
"alg":"HS256"
}
JWT payload
The payload is the actual data of the JWT. It consists of a list of claims - each claim is a name-value pair.
A claim can be either standard OpenID Connect or custom (with its own namespace).
{
"sub": "tony@superoffice.com",
"http://schemes.superoffice.net/identity/associateid": "5",
"http://schemes.superoffice.net/identity/identityprovider": "central-superid",
"http://schemes.superoffice.net/identity/email": "tony@superoffice.com",
"http://schemes.superoffice.net/identity/upn": "tony@superoffice.com",
"http://schemes.superoffice.net/identity/is_administrator": "False",
"http://schemes.superoffice.net/identity/ctx": "Cust26759",
"http://schemes.superoffice.net/identity/company_name": "Tonys Developer Network",
"http://schemes.superoffice.net/identity/serial": "1801550193",
"http://schemes.superoffice.net/identity/netserver_url": "https://sod.superoffice.com/Cust26759/Remote/Services86/",
"http://schemes.superoffice.net/identity/webapi_url": "https://sod.superoffice.com/Cust26759/api/",
"http://schemes.superoffice.net/identity/system_token": "SuperOffice DevNet Node OIDC-8k8Q7DmBgo",
"iat": "1581665207",
"http://schemes.superoffice.net/identity/initials": "TY",
"http://schemes.superoffice.net/identity/so_primary_email_address": "tony@superoffice.com",
"nonce": "637172620046685267.NmU2ZmRjNTctYjU0ZS00ZDRlLThkNjgtOTBlZmY2N2QyYjc3MzYzZWE1YjctYTUxYS00NDM1LWE1YTEtNDEzYTMxNTgxMzA0",
"nbf": 1581665147,
"exp": 1581665507,
"iss": "https://sod.superoffice.com",
"aud": "6cf25376616343b38d14ddcd804f2891"
}
SuperOffice-specific claims
SuperOffice offers the following in addition to the standard OAuth claims. The Federated ID column represents the legacy federated flow, which no one should be using any more.
Note
The claims in the following table are all prefixed with http://schemes.superoffice.net/identity/
Claim name | Federated ID | OpenID Connect | Description |
---|---|---|---|
associateid |
X | X | The current user's associate ID. |
company_name |
X | X | The current user's company name. |
ctx |
X | X | The ctx claim is the context identifier, which is also the tenant identifier or Customer ID. Example Cust1234. |
email |
X | X | The current user's email address. |
firstname |
X | The current user's first name. | |
identityprovider |
X | X | The identity provider responsible for authentication. Options: SuperOffice AS (federated ID) https://sod.superoffice.com (OpenID Connect) |
initials |
X | X | The current user's full name initials. (added June 2019) |
is_administrator |
X | X | Determine whether the current user is an administrator. |
lastname |
X | The current user's last name. | |
netserver_url |
X | X | The URL to a tenant SOAP web service. Often used in conjunction with SuperOffice .NET NuGet proxies. New applications should always use the latest. |
remember_me_expires |
X | X | Unused. |
serial |
X | X | The tenant database serial number. |
so_primary_email_address |
X | X | The current user's primary email address. (added June 2019) |
system_token |
X | X | A unique identifier used to exchange for a system ticket. Used for background processing, back-channel communications. |
ticket |
X | A current user's unique identifier, used for authentication. | |
upn |
X | X | Specifies a user principal name (UPN). |
webapi_url |
X | X | The URL to a tenant REST web services. |
JWT signature
Signatures verify that the information was sent from the sender and that the information has not been altered.
What does it mean to validate tokens?
- Verify the JWT is well-formed (has 3 period-separated sections)?
- Parse the string and extract the Base 64 encoded components - are they valid JSON?
- Is the signature OK?
- Are the standard claims OK? Check there is a required sub claim and other OIDC claims.
- Check the namespace-specific claims.
If any of these tests fail, the JWT should be rejected and not trusted.
Various SuperOffice services define different validation parameters used for performing validation.
How to validate security tokens
Security token validation is an important step to ensure the token has not been compromised between SuperOffice sending it and you receiving it.
Performing validation is a straightforward process that must occur for each response that was signed by SuperOffice.
There are a couple of options to perform the actual validation:
Use explicit validation.
- Use physical SuperOffice certificates.
- Use the OpenID Connect metadata endpoint to get the public certificate information from the
jwks_uri
endpoint.
Use SuperOffice.WebApi NuGet package written for .NET Standard 2.0.
- This uses the OpenID Connect metadata endpoint.
Use SuperOffice.Online.Core NuGet package for .NET Framework.
- This requires SuperOffice certificates.
Explicit validation
This option eliminates any dependency on third party libraries to perform validation. There are three components necessary to perform token validation:
- Issuer
- Audience
- Certificate
The values used to populate validation parameters will vary depending on which SuperOffice token is validated. The table below will help guide you.
Validation parameters
Scenario | Issuer | Audience | SigningKey |
---|---|---|---|
OAuth 2.0 / OpenID Connect | https://{env}.superoffice.com | Application ID | Public certificate |
System User | SuperOffice AS | spn:{serial claim value} | Public certificate |
Connectors | SuperOffice AS | spn:Application ID | Public certificate |
Database Mirroring | SuperOffice AS | spn:Application ID | Public certificate |
For ease of understanding, rather than hard-code these values in the code sample below, the issue and audience values are extracted from the token itself, then used to set the appropriate TokenValidationParameters
properties.
This sample code has a System.IdentityModel.Tokens.Jwt NuGet package dependency.
namespace Example
{
/// <summary>
/// SuperOffice jwt token validator
/// </summary>
internal class SampleValidator
{
public Microsoft.IdentityModel.Tokens.TokenValidationResult ValidateSuperOfficeToken(string token)
{
var securityTokenHandler =
new Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler();
string issuer;
string audience;
// extract the ValidAudience claim value (database serial number).
var securityToken = securityTokenHandler.ReadJsonWebToken(token);
// get the audience from the token
if (!securityToken.TryGetPayloadValue<string>("aud", out audience))
{
throw new Microsoft.IdentityModel.Tokens.SecurityTokenException(
"Unable to read ValidAudience from token.");
}
// get the issuer from the token
if (!securityToken.TryGetPayloadValue<string>("iss", out issuer))
{
throw new Microsoft.IdentityModel.Tokens.SecurityTokenException(
"Unable to read ValidAudience from token.");
}
var validationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters();
validationParameters.ValidAudience = audience;
validationParameters.ValidIssuer = issuer;
// Option #1 *************************************************************
// use the local SuperOffice public certificate (SuperOfficeFederatedLogin)
var certPath = Path.Combine("Certificates", "SuperOfficeFederatedLogin.crt");
var x509Cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath);
validationParameters.IssuerSigningKey = new Microsoft.IdentityModel.Tokens.X509SecurityKey(x509Cert);
// Option #2 *************************************************************
// use the OpenID Connect Jwks endpoint to get the public certificate.
validationParameters.IssuerSigningKeys = GetJsonWebKeys("sod");
var result = securityTokenHandler.ValidateToken(token, validationParameters);
if (result.Exception != null || !result.IsValid)
{
throw new Microsoft.IdentityModel.Tokens.SecurityTokenValidationException(
"Failed to validate the token", result.Exception);
}
return result;
}
private IList<JsonWebKey> GetJsonWebKeys(string environment)
{
// example only... needs exception handing...!!!
var client = new HttpClient();
var jwksContent = client.GetStringAsync($"https://{environment}.superoffice.com/login/.well-known/jwks");
return JsonWebKeySet.Create(jwksContent.Result).Keys;
}
}
}
Using SuperOffice.WebApi
This NuGet package contains 2 validation classes, one for each of the 2 main validation case:
- OpenID Connect validation:
JwtTokenHandler
- SystemUser Flow validation:
SystemUserTokenHandler
There are 2 different token handlers because they slightly different implementations. The difference is that the JwtTokenHandler
uses the client_id for a ValidAudience, whereas the SystemUserTokenHandler
uses the database serial number as the ValidAudience. The latter requires additional processing to extract the database serial number from the token.
var tokenHandler = new JwtTokenHandler(clientId, httpClient, onlineEnvironment);
TokenValidationResult result = await tokenHandler.ValidateAsync("{id_token}");
var tokenHandler = new SystemUserTokenHandler(httpClient, onlineEnvironment);
TokenValidationResult result = await tokenHandler.ValidateAsync("{system_user_result}");
Using SuperOffice.Online.Core
Caution
This is approach is considered legacy. We recommend you now use one of the options above, explicit validation or the SuperOffice.WebApi libraries.
Legacy security tokens are either a JWT or SAML token. We strongly recommend that you use JWT tokens! SAML token support is deprecated.
The main class for processing tokens is SuperIdTokenHandler
in the SuperOffice.SuperID.Client DLL.
Click to download the SuperOffice certificates (ZIP file).
Note
If you don't have access to the certificate store, you must substitute this procedure with a validation override.
Pre-requisites
- Either all 3 certificates are installed correctly, or you override the default certificate chain used to perform validation.
- A correct thumbprint is defined in the SuperIdCertificate appSettings section.
Procedure
Decode the token from Base64 to a string. This results in a JSON string.
Use an appropriate certificate validation library and the public SuperOffice certificate to validate the token:
- Instantiate a
SuperIdTokenHandler
. - Invoke the
ValidateToken
method and pass a JWT token.
- Instantiate a
If and only if the token is valid, accept the claims and proceed accordingly:
- Receive a SuperIdToken populated with the resulting claims.
public SuperIdToken ValidateToken(string token)
{
var tokenHandler = new SuperIdTokenHandler();
return tokenHandler.ValidateToken(token, TokenType.Jwt);
}
Instead of depending on certificates in the local certificate store, you can download the public certificates, including SuperOfficeFederatedLogin.crt, to perform token validation.
public static SuperIdToken ValidateToken(string token)
{
var tokenHandler = new SuperIdTokenHandler();
tokenHandler.JwtIssuerSigningCertificate = new X509Certificate2("Certificates\\SuperOfficeDevelopment.crt");
tokenHandler.ValidIssuer = "https://sod.superoffice.com";
tokenHandler.CertificateValidator = X509CertificateValidator.None;
tokenHander.ValidateAudience = false;
return tokenHandler.ValidateToken(token, TokenType.Jwt);
}
If you for some reason need to use SAML tokens, simply substitute token type in step 2 (TokenType.Saml
) and pass your SAML token. SuperIdTokenHandler
hides the slight differences between SAML and JWT tokens.
public static SuperIdToken ValidateToken(string token)
{
var tokenHandler = new SuperIdTokenHandler();
// Optionally override using certificate store, and use local certificate instead
var certificateResolverPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Certificates");
tokenHandler.CertificateValidator = System.IdentityModel.Selectors.X509CertificateValidator.None;
tokenHandler.IssuerTokenResolver = new SuperOffice.SuperID.Client.Tokens.CertificateFileCertificateStoreTokenResolver(certificateResolverPath);
tokenHander.ValidateAudience = false;
return tokenHandler.ValidateToken(token, TokenType.Saml);
}
SuperIdToken
The SuperIdToken
class is a container for security claims. It is returned after the validation of a JWT (or SAML) token and can be used for future authentication.
Caution
This legacy class pertains to our old form of authentication. We recommend you now use Use OpenID Connect.
SuperIdToken
contains:
- individual properties for common claims
- a complete list of claims returned by SuperOffice CRM Online
public class SuperIdToken
{
public Claim[] Claims { get; }
public string IdentityProvider { get; }
public string Ticket { get; }
public string NetserverUrl { get; }
public string SystemToken { get; }
public string Email { get; }
public string ContextIdentifier { get; }
}
Ticket: a SuperOffice ticket, representing the current user credential on this particular customer; not included in OAuth flows
NetServer_URL: the SOAP web service endpoint for the current customer site
WebAPI_URL: the RESTful endpoints for the current customer site (replaces
NetServer_URL
in classSuperIdToken
)Email: the current user’s email address
ContextIdentifier (CTX): context value (current customer ID).
System User Token: a string used to exchange for a system user ticket credential
The SuperIdToken data type is located in the SuperOffice.SuperID.Client.Tokens
namespace in the SuperOffice.Online.Core assembly.
Assemblies and helper libraries
SuperOffice provides the SuperOffice.Crm.Online.Core NuGet for processing online requests. It contains the following assemblies:
- SuperOffice.Online.Core
- SuperOffice.SuperID.Client
- SuperOffice.SuperID.Contracts
We also provide .NET helper libraries, which you can download.