2. Authentication using JSON Web Tokens (JWT)

Previous chapter

GemStone/S 64 Bit vers 3.7.4 adds the ability to login using JSON Web Tokens (JWTs).

This feature should be considered preview in this release. While JWT logins have been carefully tested, the API is subject to change.

A JSON Web Token (JWT) is encoded JSON that contains a set of required and optional key/value pairs, and is digitally signed using a key. GemStone authentication supports signing with an RSA public/private key pair.

The JWT’s JSON contents has three sections: the header, the payload, and the signature. The JWT is transmitted in Base64Url encoded format, with each section separately encoded and period-delimited as EncodedHeaderSection.encodedPayLoad.encodedSignature. This encoded string is what is meant by JWT in this document.

Authentication in GemStone using JWTs

JWTs can be used for authentication in GemStone the way a GemStone password is used, for UserProfiles that are configured with this authentication scheme. Since some of the authentication must be done before the UserProfile is accessed, there is an additional flag that must be set for login using JWT.

JWT authentication is supported when logging in from topaz, GBS v8.9alpah1 and later, GsTsExternalSession, and using the classic and thread-safe GCIs.

JWT authentication cannot be used for system UserProfiles (SystemUser, DataCurator, GcUser, SymbolUser, Nameless, and HostAgentUser).

In addition to using the JWT as a password, GemStone login now allows alternative password source locations; you may set an environment variable to reference the encoded JWT, or the encoded JWT can be put in a disk file, or the encoded JWT can be embedded as the value in a JSON key-value pair in a disk file. For more details on the new password source location support, see Additional Login Parameter Support.

To use JWTs for GemStone authentication, you must do the following:

Acquire a JWT

Generally, you will acquire the JWT from a source outside GemStone. This JWT service also provides the public key corresponding to the private key used to sign the JWT, via OpenId discovery documents. The details of acquiring a JWT from an OpenId service provider is outside the scope of this document. See JWT Tokens for minimum expected information.

Using the JsonWebToken class, you can create a JWT and sign it using your own private key, and configure the Stone with the corresponding public key. This allows the hand-constructed JWT to be used for authentication. This mechanism is designed for testing; keys added to the Stone in this way are temporary; they do not persist across Stone restart. These temporary JWTs are not authenticated outside of GemStone and do not require OpenId discovery configuration.

Configure the Stone

For JWT authentication using OpenId document discovery to acquire public keys, the Stone must be configured for JWT public key discovery. See Configuring the Stone to support JWTs.

No Stone configuration is required for authentication using public keys manually added to support temporary JWTs.

Configure the UserProfile with a JwtSecurityData

The UserProfile must be configured specifically for the JWT requirements of the JWTs that will be used for login.

You must create a JwtSecurityData object and configure it with the accepted values for each claim that will be defined in the JWT. This JwtSecurityData is added to the GemStone UserProfile when configuring it to authenticate via JWT.

SeeConfigure the UserProfile with a JwtSecurityData.

Configure login parameters

Unlike other types of authentication for which the UserProfile’s configured authentication is recognized, JWT authentication must be explicitly enabled in the client before login.

Note that there are additional password sources supported in v3.7.4, including passwords in filenames or environment variables; which may optionally also be configured.These are described under Additional Login Parameter Support.

The specifics of logging in depends on the client:

  • To login using a JWT from topaz, use added topaz commands. See Login via Topaz.
  • To login using a JWT from an external session, use added methods in GsTsExternalSession and GemStoneParameters. Login using GsExternalSession is not supported for JWT logins. See Login via GsTsExternalSession.
  • To login using a JWT in a GCI application, there are additional login flags. See Login via GemBuilder for C.
  • X509-secured logins are not currently supported for JWT authentication.

JWT Tokens

The JWT header and payload sections contains required claims, and may also contain an arbitrary number of optional claims. The following claims are required

Table 1 Required JWT claims

Claim Key

Section

Required Status

 

"kid"

header

required, automatically managed when using OID public keys.

String with key ID

"alg"

header

required

"RS256"

"typ"

header

required

"JWT"

"exp"

payload

required; automatically enforced.

Number defining the expiration time

"iss"

payload

required, and claim must be configured in JwtSecurityData.

String with issuer

"aud"

payload

required, and claim must be configured in JwtSecurityData.

String with audience

The JSON, for example would be something like this:

{
"header":
	{
	"kid":"theId",
	"alg":"RS256", 
	"typ":"JWT"
	},
"payload":
	{
	"exp":expiration,
	"iss":"URLOfIssuer",
	"aud":"intendedAudience", 
	},   
"signature": aByteArray
}

Additional payload claims

The JWT will also normally contain additional payload claims, which depend on the service generating the JWT.

If an optional claim does not have a corresponding claim in the JwtSecurityData used for validating login, then the JWT claim is ignored for login.

The exception is "nbf", Not Before Time. "nbf" is not required, but if the JWT includes "nbf", the value is automatically enforced during login; this does not require configuration on the JwtSecurityData.

Ordering of JWT claims

The order of the fields in JSON and a JWT is not defined or intended to be meaningful. By convention, some fields are ordered before others, but this is not guaranteed. This means that the encoded JWT, and any JSON printed from the JWT, may not be identical. This does not affect the utility of the JWT; the login process relies on the existence of the keys and not on the specific layout within the JSON.

More information

For more on JWTs, including the commonly used additional claim keys, see:

https://auth0.com/docs/secure/tokens/json-web-tokens

https://www.rfc-editor.org/rfc/rfc7519.txt

2.1  Configuring the Stone to support JWTs

To allow login using a JWT that was signed with a private key, the Stone needs to have access to the corresponding public key.

The public key discovery mechanism is handled by a new thread in the stone, the OpenId Discovery (OID) thread. This thread handles discovery of the OpenID documents, and updates the list of public keys stored in the Stone to add new keys and remove old keys.

Temporary keys that are manually added to the Stone are handled separately and are not affected by the OID thread.

JWTs authenticated via OpenId discovery require that the Stone must be configured with one or more STN_OPENID_DISCOVERY_URLS; without this, the OID thread does not start.

Public Key Discovery

Providers of JWTs share the public keys through a key discovery URL, from which can be found the URLs of JSON Web Key Set (JWKS) documents, which contain one or more public keys.

JWT providers periodically rotate keys. Most JWKS documents which provide public keys for verifying JWTs contain two public keys at any one time: the public key currently in use and the next public key to be used sometime in the future. The period of validity for these public keys is specified by the key provider. A typical duration is 30 days, however some providers rotate keys as frequently as once per hour or as seldom as once per year.

The Stone maintains a list of public keys corresponding to the key Ids that are issued in JWTs. The public keys are read by the OID thread, which is responsible for making the TLS handshake to the key discovery URL and parsing the resulting JSON. Since the public keys are rotated, the table of public keys must be periodically refreshed.

OpenId Discovery Thread

The OID thread is responsible for making the connection to the discovery document server, parsing the resulting file, keeping track of currently valid public keys, and periodically refreshing these keys.

When the Stone is configured with one or more OpenId discovery URLs (STN_OPENID_DISCOVERY_URLS), the OpenId Discovery (OID) thread is started. This thread writes to its own log file, stoneName_stonePIDopenidthd.log. If STN_OPENID_DISCOVERY_URLS is not defined, the OID thread does not start.

At Stone startup time, the OID thread queries all configured URLs in STN_OPENID_DISCOVERY_URLS to obtain the OpenId discovery documents. The specified URLs may reference either a JSON key discovery document or a JSON JWKS document. No other document formats are accepted.

These documents are parsed to collect the elements of the public keys, which are stored in the Stone. These public keys are used to authenticate with the private key in the JWT signing certificate.

If any of the URLs cannot be read or the documents found cannot be parsed, Stone startup will fail. After the Stone has started, failure to access and parse the documents is not a fatal error; a warning is printed to the OID thread log. JWT login attempts will use the previously loaded public keys.

Configuring Stone

Configuring the discovery URL or URLs is done with the Stone’s configuration parameter STN_OPENID_DISCOVERY_URLS. This must be done before Stone starts.

For example, if using google’s openID, add the following line to system.conf:

STN_OPENID_DISCOVERY_URLS = "https://accounts.google.com/.well-known/openid-configuration";

The configuration parameter STN_OPENID_DISCOVERY_INTERVAL sets the frequency that the discovery documents at the given URL or URLs is refreshed. The default of once per hour is used, if this is not explicitly defined. See STN_OPENID_DISCOVERY_INTERVAL

Accessing the OpenId discovery document

Although no authentication is required to access the OpenId discovery document, most sites host this document on a web server which allows only secure (https) connections. In order for the OID thread to connect with hosts in STN_OPENID_DISCOVERY_URLS, a TLS handshake is required. This handshake requires access to the trusted root certificate bundle file on the Stone’s host.

The Stone startup will fail if the root certificate bundle file cannot be found or does not enable the handshake to access the OpenId discovery document.

By default, the Stone will look in the default location per the OS, so on most systems, nothing further is required. If you have a different setup, you must set the correct path; see STN_OPENID_DISCOVERY_CA_CERT_FILE.

You may also specify the location by setting the environment variable GS_CURL_CA_FILE to reference the location of the certificate bundle file. This must be set before the Stone starts up. If this environment variable is defined and the GS_CURL_CA_FILE destination is invalid, Stone startup will fail.

Special case on Darwin

On Darwin, the required certificate bundle is not installed by default. MacOS has its own proprietary framework for managing X.509 certificates (key chain), which is not used by OpenSSL. Darwin users must install the open source homebrew package “ca-certificates” in order to acquire a valid certificate bundle. Note that many open source Darwin packages also require this package, so it may already be installed on development systems.

JWT related queries on the Stone

The following methods provide support for repository-wide JWT support in the Stone.

System class >> jwtKeyRefreshEnabled
Return a boolean indicating if the Open ID discovery thread in stone is enabled for this repository. If this returns false, only manually-added temporary JWT public keys will allow login via JWT.

System class >> forceOpenIdKeyRefresh
Forces the Open ID refresh thread in stone to refresh the list of public keys used to authenticate JSON Web Token (JWT) logins. Returns true on success or false if discovery-based JWT logins are not enabled.

System class >> jwtLoginsEnabled
Return a boolean indicating if logins with JSON Web Tokens (JWTs) via OpenId discovery are enabled for this repository.

System class >> jwtPublicKeys
Returns the keys and key ids maintained by stone to validate JWT logins.
There are two lists of keys in stone:
   1 - keys obtained by the Open ID Discovery thread.
   2 - temporary keys manually added to the Stone.
The result of this method includes keys from both lists. The result is an Array of pairs where the odd numbered elements are key IDs (Strings) and the even numbered elements are instances of GsTlsPublicKey.

Added Configuration Parameters

The following configuration parameters have been added to support JWT logins.

The STN_OPENID_DISCOVERY_URLS parameter is required to enabled OpenId discovery based for JSON Web Tokens (JWTs) for login authentication; other parameters have defaults. If the parameters are defined, the values must be configured to support JWT authentication. These parameters are not required to use temporary testing JWTs.

STN_OPENID_DISCOVERY_CA_CERT_FILE

Specifies the location of the CA certificates file to be used when sending requests to obtain public keys to the URLS specified in the STN_OPENID_DISCOVERY_URLS configuration parameter.

If no option is specified (the default), the stone looks in the default locations:

Linux (Fedora: Red Hat, etc):

/etc/pki/tls/certs/ca-bundle.crt

Linux (Debian: Ubuntu, etc):

/etc/ssl/certs/ca-certificates.crt

MacOS (Darwin):

/opt/homebrew/etc/ca-certificates/cert.pem
$HOMEBREW_PREFIX/etc/ca-certificates/cert.pem

While this configuration parameter is optional. if it is set, it must be set to an existing file; otherwise Stone startup will fail.

STN_OPENID_DISCOVERY_INTERVAL

Specifies the frequency in minutes that the OpenId discovery thread in stone will refresh the discovery documents referenced by STN_OPENID_DISCOVERY_URLS. Discovery documents are always refreshed at Stone startup time. This parameter has no effect unless STN_OPENID_DISCOVERY_URLS is specified.

Runtime equivalent: #StnOpenIdDiscoveryInterval
(requires SystemControl privilege)
Default: 60
Min: 1
Max: 1000000
Units: Minutes

STN_OPENID_DISCOVERY_URLS

Specifies a list of URLs used to obtain OpenID discovery documents (OIDD) or JSON Web Key Sets (JWKS). Each URL specified must reference either a OIDD or JWKS. OIDDs contain the URL address of a JWKS and are preferred.

URL arguments must be in this format:

"https://<url>"

One or more entries are required in order to use OpenJSON Web Tokens (JWTs) as login credentials. Discovery documents are used to obtain public keys that are required in order to verify the signatures of JWTs.

Default: None

2.2  Configure the UserProfile with a JwtSecurityData

To enable JWT authentication for a UserProfile, you must create and configure an instance of the new class JwtSecurityData, and add this to the UserProfile. This JwtSecurityData must have the acceptable values defined for all the claims in the JWT that will be used for login.

JwtSecurityData

The class JwtSecurityData encapsulates the specific security requirements to allow login with a JWT. The actual claims in a JWT will depend on the JWT OpenId service provider that provides the JWT, and the details used to request the JWT from the OpenId service.

Each claim in the JWT must have a corresponding key in the JwtSecurityData, and the JWT’s value at that key must match the JwtSecurityData’s allowed values.

The JWT’s expiration, and not before time (nbt) if present, are automatically checked and do not need to be configured in the JwtSecurityData.

A JwtSecurityData is configured with:

  • One or more issuers
    validIssuers is an IdentitySet of Symbols containing acceptable values for the 'iss' JWT member. If the wildcard #* is included, any value will be accepted.

To add an issuer, use addIssuer: stringOrSymbol. You may call this more than once to allow multiple issuers.

  • One or more audiences
    validAudiences is an IdentitySet of Symbols containing acceptable values for the 'aud' JWT member. If the wildcard #* is included, any value will be accepted.

To add an audience, use addAudience: stringOrSymbol. You may call this more than once to allow multiple audiences.

  • One or more userIds
    validUserIds is an IdentitySet of Symbols containing acceptable values for the userId, which is the JWT member at the receiver’s setting for userIdKey. The userIdKey is 'aud' by default. If the wildcard #* is included, any value will be accepted. This does not need to match the UserId of the UserProfile to which this JwtSecurityData will be used.

To add an userId, use addUserId: stringOrSymbol. You may call this more than once to allow multiple userIds.

  • Zero or more user claims
    userClaims is an Array of instances of JwtUserClaim Objects. Each claim specifies the corresponding JWT key and the accepted values. User claims are optional but if present in the JwtSecurityData, the JWT will be validated for that claim.

To add a claim, use addUserClaim: aJwtUserClaim. You may call this more than once to allow multiple claims.

Basic JwtSecurityData configuration

The required portion of a JwtSecurityData is configured by the methods:

JwtSecData >> addAudience: audience; 
JwtSecData >> addIssuer: issuer; 
JwtSecData >> addUserId: userIdOfAUserProfile.

By default, the JwtSecurityData will look for the userId under the 'aud' in the JWT; this is only required if using a different JSON key for the UserId in the JWT.

For example:

aJwtSecData:= JwtSecurityData new. 
aJwtSecData 
addAudience: 'GsAdminUser'; 
addIssuer: 'https://accounts.google.com';
userIdKey: 'aud'; 
addUserId: 'GsAdminUser'.

JwtUserClaims

Your JWT will normally include additional user claims. These are optional, but if there are claims in the JWT that are not configured in the JwtSecurityData, login will fail. Claims in the JWT that are not of concern for GemStone authentication can be configured with a wildcard to allow the JWT validation on that claim to pass.

The class JwtUserClaim has been added to flexibly support user claims. Each claim has the following options:

jsonKey
A Symbol matching the key for the claim in the JWT. This must exist in the JWT, or login will fail.

jsonKind
A symbol describing the type of JSON object for the value of the key. Must be one of #String, #Number, #ArrayOfStrings, #ArrayOfNumbers, or #Boolean.

If the jsonKind is set to #ArrayOfStrings or #ArrayOfNumbers, the JWT’s value can be either an Array or a single value of String or Number.

acceptedValues
an Array of one or more Symbols that includes all acceptable values. Each value must be a Symbol. If the JWT’s value at #jsonKey is not in the list of values, login will fail. If the wildcard #* is included, any value will be accepted.

To add a claim, create it using class methods:

JwtUserClaim class >> newWildcardClaimWithJsonKey: keySym jsonKind: kindSym 
JwtUserClaim class >> newWithJsonKey: keySym jsonKind: kindSym acceptedValues: anArray 

And add the claim to the security Data using:

JwtSecurityData >> addClaim:

See the image methods for JwtUserClaim to see the protocol for further modifying the configuration and for further specifying wildcards.

For example:

claim := JwtUserClaim 
newWithJsonKey: #sub 
jsonKind: #String 
acceptedValues: { #'115156884418451143667' }.
aJwtSecData addUserClaim: claim.

Enable JWT Authentication in the UserProfile

To enable JWT authentication for a user, a new method has been added:

UserProfile >> enableJwtAuthenticationWith: aJwtSecurityData

Executing this method enables JWT authentication with the given aJwtSecurityData. A deep copy of aJwtSecurityData is made, and all elements securityPolicy is set to the SystemSecurityObjectSecurityPolicy.

System users, including SystemUser, DataCurator, GcUser, SymbolUser, Nameless, and HostAgentUser, cannot be configured to authenticate using JWTs.

Users configured with JWT Authentication can be disabled by administrators the same away as other non-GemStone-authentication accounts are managed.

The following methods allow you to fetch the security data and collect information about a UserProfiles’s JWT authentication.

UserProfile >> authenticationSchemeIsJWT
Returns true if the receiver is configured to authenticate using a JWT.

UserProfile >> authenticationScheme
Returns #JWT for UserProfiles with JWT authentication.

UserProfile >> authenticationScheme: aScheme
This method now accepts #JWT to configure JWT authentication.

UserProfile >> jwtSecurityData
Returns a deep copy of the UserProfile’s current JwtSecurityData, or nil if the receiver is not configuredd to authenticate with JWT.

UserProfile >> validateJwtPassword: aJwtString
Validates a JWT for login as the receiver. The receiver must have JWT authentication enabled. aJwtString must be a String which is a valid JsonWebToken in base64url format. Returns true if the JWT password is valid for the receiver. Otherwise returns a String describing an error condition.

Modifying a JwtSecurityData

You may make changes to an already-configured UserProfile’s JwtSecurityData. Note that the act of setting and fetching both make copies, you do not modify the actual instance in use.

You may fetch a copy of the current JwtSecurityData using aUserProfile jwtSecurityData. This is a deep copy and can be edited freely. After modifying, invoke enableJwtAuthenticationWith: again with the updated JwtSecurityData, to update the UserProfile. This step also makes a deep copy, so futher edits will not affect the UserProfile.

See the image methods for JwtSecurityData to see the protocol for further modifying the configuration.

Example 2.1 Enable JWT Authentication for a UserProfile

Enable authentication for an existing user GsAdminUser,

| jwtSecData claim user |
 
aJwtSecData:= JwtSecurityData new. 
aJwtSecData 
addAudience: 'GsAdminUser'; 
addIssuer: 'https://accounts.google.com';
userIdKey: 'aud'; 
addUserId: 'GsAdminUser'. 
 
claim := JwtUserClaim 
newWithJsonKey: #sub 
jsonKind: #String 
acceptedValues: { #'115156884418451143667' }. 
aJwtSecData addUserClaim: claim.
 
claim := JwtUserClaim 
newWithJsonKey: #azp 
jsonKind: #String 
acceptedValues: { #'115156884418451143666' } . 
aJwtSecData addUserClaim: claim. 
 
claim := JwtUserClaim 
newWithJsonKey: #email 
jsonKind: #String 
acceptedValues: { #'service-account@oauth2-test-436914.iam.gserviceaccount.com' }. 
aJwtSecData addUserClaim: claim. 
 
claim := JwtUserClaim 
newWithJsonKey: #email_verified 
jsonKind: #Boolean 
acceptedValues: { true } . 
aJwtSecData addUserClaim: claim. 
 
(AllUsers userWithId: 'GsAdminUser') enableJwtAuthenticationWith: aJwtSecData. 
 
System commit
 

2.3  Logging In

Password Source Locations

In 3.7.4, GemStone adds support for passwords to be located in environment variables or in disk files, in addition to being entered directly.

The environment variable name or disk file path (relative or absolute) is put into the login parameters password field (this parameter name is no longer entirely accurate, but retained for compatibility). When using a password in a disk file, the file should only include the password.

You must also set the appropriate login flag to indicate the password source location. how this is done depends on the client and is described in later sections.

JWT in JSON (JSON-wrapped JWT)

Passwords located in environment variables or on disk apply to most Authentication Scheme passwords (GemStone-native, UNIX, LDAP, and JWT), and can be used for login in all client environments (Topaz, GBS, GCI/GemBuilder for C, and external sessions).

For JWT logins only, there is an addition file-based option. It is allowed for the filename to refer to a file containing JSON, in which a key in the top level of the JSON refers to the JWT. Logins using JSON-wrapped JWT are available from Topaz and GBS only.

The JSON-wrapped JWT contents of this file might have the form, for example:

Example 2.2 JSON-wrapped JWT

os$> cat /gshost/gemstone/jwtlogin.jsonpass
{
"access_token":
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImRkMTI1ZDVmNDYyZmJjNjAxNGFl
ZGFiODFkZGYzYmNlZGFiNzA4NDciLCJ0eXAiOiJKV1QifQ.eyJhdWQiOi
JMaXNhIiwiYXpwIjoiMTE1MTU2ODg0NDE4NDUxMTQzNjY2IiwiZW1haWw
iOiJzZXJ2aWNlLWFjY291bnRAb2F1dGgyLXRlc3QtNDM2OHE0LmlhbS5n
c2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsI
mV4cCI6MTczNjgzNDk-3OCwiaWF0IjoxNzM2ODMxMzc4LCJpc3MiOiJod
-HRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTUxNTY4O
DQ0MTg0NTExNDM2NjYifQ.Ml0Cdx01k4Z1T5PoHdaioXE1Bab_e9SITXR
Fj3tUVg09JtywU3X88ozhz3aKnUF_CT6e8ZSNjDqSjy9AlMjGEnxgMUVa
0slIXM7wTg4jcoKikJ0uAadi6RKX9KZBLl0yBfc8xE92svWF6RV3tBeKi
Uyu9CcIVsMQZrDMes2nf7zFoE3sNrh9x7AHlopS5UW5gm4SXJO3CDesIJ
5jqkLDFuvkMPWZm8maXrYeDVWQHb5fspa4EZ4YPJVTwwFuLxQZ_CPfluS
psaWHtymzfVyQU7Pj6oSFJ5UwFxFOLixsU_sK2GsnxLivxr9hotFkn8h8
GjQ0OXj5bBtbU_7A",
"scope": "https://www.googleapis.com/auth/prediction",
"token_type": "Bearer",
"expires_in": 3600
} 

When using a JSON-wrapped JWT to login, you must specify both the name of the file and the key that refers to the JWT.

Login via Topaz

Topaz includes additional set subcommands to configure login. For some additional details, see Topaz added set subcommands.

SET ENVPASSWORD onOrOff

Set to ON to treat password field as an envionrment variable containing a password.

SET FILEPASSWORD onOrOff

Set to ON to treat password field as the path to a disk file containing a password.

SET JWTJSONFILENAME filename key

Enables login using a JWT that is in a file with the name filename, which contain JSON. The JSON must include a key key that references the encoded JWT that to be used as the password for login. Executing this command makes topaz parse the JSON file and set the password to be the JWT. It also sets JWTPASSWORD to ON. May be abbreviated as JWTJ.

SET JWTPASSWORD onOrOff

When on, enables login using a JWT. A JWT password is valid for a specific user for a period of time until the JWT expires, and requires configuration of the Stone and UserProfile. May be abbreviated as JWTP or JWTPASS.

Examples of the Topaz JWT authentication options

To login with JWT encoded string
topaz> set jwtpass on
topaz> set user GsAdminUser
topaz> set password eyJhbGciOiJSUzI1NiIsImtpZCI6ImRkMTI1ZDVmNDY
yZmJjNjAxNGFlZGFiODFkZGYzYmNlZGFiNzA4NDciLCJ0eXAiOiJKV1QifQ.
eyJhdWQiOiJMaXNhIiwiYXpwIjoiMTE1MTU2ODg0NDE4NDUxMTQzNjY2Iiwi
ZW1haWwiOiJzZXJ2aWNlLWFjY291bnRAb2F1dGgyLXRlc3QtNDM2OHE0Lmlh
bS5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUs
ImV4cCI6MTczNjgzNDk3OCwiaWF0IjoxNzM2ODMxMzc4LCJpc3MiOiJod-HR
wczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTUxNTY4ODQ0MTg
0NTExNDM2NjYifQ.Ml0Cdx01k4Z1T5PoHdaioXE1Bab_e9SITXRFj3tUVg09
JtywU3X88ozhz3aKnUF_CT6e8ZSNjDqSjy9AlMjGEnxgMUVa0slIXM7wTg4j
coKikJ0uAadi6RKX9KZBLl0yBfc8xE92svWF6RV3tBeKiUyu9CcIVsMQZrDM
es2nf7zFoE3sNrh9x7AHlopS5UW5gm4SXJO3CDesIJ5jqkLDFuvkMPWZm8ma
XrYeDVWQHb5fspa4EZ4YPJVTwwFuLxQZ_CPfluSpsaWHtymzfVyQU7Pj6oSF
J5UwFxFOLixsU_s-K2GsnxLivxr9hotFkn8h8GjQ0OXj5bBtbU_7A
topaz> login
To login with the encoded JWT in an environment variable
os$ > export GSAPASS=
eyJhbGciOiJSUzI1NiIsImtpZCI6ImRkMTI1ZDVmNDYyZmJjNjAxNGFlZGFi
ODFkZGYzYmNlZGFiNzA4NDciLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJMaXNhI
iwiYXpwIjoiMTE1MTU2ODg0NDE4NDUxMTQzNjY2IiwiZW1haWwiOiJzZXJ2a
WNlLWFjY291bnRAb2F1dGgyLXRlc3QtNDM2OHE0LmlhbS5nc2VydmljZWFjY
291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTczNjgzN
Dk3OCwiaWF0IjoxNzM2ODMxMzc4LCJpc3MiOiJod-HRwczovL2FjY291bnRz
Lmdvb2dsZS5jb20iLCJzdWIiOiIxMTUxNTY4ODQ0MTg0NTExNDM2NjYifQ.M
l0Cdx01k4Z1T5PoHdaioXE1Bab_e9SITXRFj3tUVg09JtywU3X88ozhz3aKn
UF_CT6e8ZSNjDqSjy9AlMjGEnxgMUVa0slIXM7wTg4jcoKikJ0uAadi6RKX9
KZBLl0yBfc8xE92svWF6RV3tBeKiUyu9CcIVsMQZrDMes2nf7zFoE3sNrh9x
7AHlopS5UW5gm4SXJO3CDesIJ5jqkLDFuvkMPWZm8maXrYeDVWQHb5fspa4E
Z4YPJVTwwFuLxQZ_CPfluSpsaWHtymzfVyQU7Pj6oSFJ5UwFxFOLixsU_s-K
2GsnxLivxr9hotFkn8h8GjQ0OXj5bBtbU_7A

The topaz commands to login are:

topaz> set jwtpass on
topaz> set user GsAdminUser
topaz> set envpass on
topaz> set pass GSAPASS
topaz> login
To login using the encoded JWT in an file

The encoded password is located in a text file.

os$> cat /gshost/gemstone/jwtlogin.pass
eyJhbGciOiJSUzI1NiIsImtpZCI6ImRkMTI1ZDVmNDYyZmJjNjAxNGFlZGFi
ODFkZGYzYmNlZGFiNzA4NDciLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJMaXNhI
iwiYXpwIjoiMTE1MTU2ODg0NDE4NDUxMTQzNjY2IiwiZW1haWwiOiJzZXJ2a
WNlLWFjY291bnRAb2F1dGgyLXRlc3QtNDM2OHE0LmlhbS5nc2VydmljZWFjY
291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTczNjgzN
Dk-3OCwiaWF0IjoxNzM2ODMxMzc4LCJpc3MiOiJod-HRwczovL2FjY291bnR
zLmdvb2dsZS5jb20iLCJzdWIiOiIxMTUxNTY4ODQ0MTg0NTExNDM2NjYifQ.
Ml0Cdx01k4Z1T5PoHdaioXE1Bab_e9SITXRFj3tUVg09JtywU3X88ozhz3aK
nUF_CT6e8ZSNjDqSjy9AlMjGEnxgMUVa0slIXM7wTg4jcoKikJ0uAadi6RKX
9KZBLl0yBfc8xE92svWF6RV3tBeKiUyu9CcIVsMQZrDMes2nf7zFoE3sNrh9
x7AHlopS5UW5gm4SXJO3CDesIJ5jqkLDFuvkMPWZm8maXrYeDVWQHb5fspa4
EZ4YPJVTwwFuLxQZ_CPfluSpsaWHtymzfVyQU7Pj6oSFJ5UwFxFOLixsU_sK
2GsnxLivxr9hotFkn8h8GjQ0OXj5bBtbU_7A

The topaz commands to login are:

topaz> set jwtpass on
topaz> set user GsAdminUser
topaz> set filepass on
topaz> set pass /gshost/gemstone/jwtlogin.pass 
topaz> login
To login using the encoded JWT in JSON within a file

The encoded JWT should be in a key within JSON. For a file with contents such as described in Example 2.2, the topaz commands would be:

topaz> set user GsAdminUser
topaz> set jwtjsonfilename /gshost/gemstone/jwtlogin.jsonpass access_token
topaz> login

Note that with the jwtjsonfilename command, you do not need to also use set password nor set jwtpass; these are set by set jwtjsonfilename.

Login via GsTsExternalSession

To login using JWT from an external session, the appropriate flags must be set on the session’s parameters (an instance of GemStone Parameters).

Note that login using a JWT within a JSON file is not supported.

You must use GsTsExternalSession; JWT logins from GsExternalSession are not supported.

GemStoneParameters changed and added methods

Methods have been added to support the new password source locations as well as JWTs; see the full list of updated methods under Additional Login Parameter Support.

To login using JWTs, you can use the following combinations:

direct password

aGemStoneParameters jwtPassword: jwt

or

aGemStoneParameters setLoginWithJwt
aGemStoneParameters password: jwt

Password in disk file

aGemStoneParameters setLoginWithJwt
aGemStoneParameters passwordFileName: fileName

or

aGemStoneParameters setLoginWithJwt
aGemStoneParameters setPasswordIsFileName 
aGemStoneParameters password: filename

Password in environment variable

aGemStoneParameters setLoginWithJwt
aGemStoneParameters passwordEnvVar: envVar 

or

aGemStoneParameters setLoginWithJwt
aGemStoneParameters setPasswordIsEnvVar
aGemStoneParameters password: envVar

GsTsExternalSession added methods

Methods have been added to support the new password source locations as well as JWTs; see the full list of updated methods under Additional Login Parameter Support.

The following methods are available:

GsTsExternalSession >> jwtPassword: aString 
GsTsExternalSession >> passwordEnvVar: aString
GsTsExternalSession >> passwordFileName: aString 

Examples using GsTsExternalSession

Using the JWT directly
| sess res |
sess := GsTsExternalSession newDefault.
sess parameters
username: 'GsAdminUser';
setLoginWithJwt;
password: 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImRkMTI1ZDVmNDYyZmJjN
jAxNGFlZGFiODFkZGYzYmNlZGFiNzA4NDciLCJ0eXAiOiJKV1QifQ.eyJ
hdWQiOiJMaXNhQGdlbXRhbGtzeXN0ZW1zLmNvbSIsImF6cCI6IjExNTE1
Njg4NDQxODQ1MTE0MzY2NiIsImVtYWlsIjoic2VydmljZS1hY2NvdW50Q
G9hdXRoMi10ZXN0LTQzNjkxNC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbS
IsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE3MzY5NzU3MTMsIml
hdCI6MTczNjk3MjExMywiaXNzIjoiaHR0cHM6Ly9hY-2NvdW50cy5nb29
nbGUuY29tIiwic3ViIjoiMTE1MTU2ODg0NDE4NDUxMTQzNjY2In0.i60x
I27dL2OpjCCw_nC0ZnBYcMvGpIXtYqhubzwLA1qfYd40tcMpOsMK28OVC
Zr3g7PkzQO31gbhPMlKUsT0fHKT30vbYRcuCHXo9gjskn2w_qUgn1y7IV
Khv0ixvCbsbilhwoHI0gMmnFInQK85iFqPPswe914_xAq26-ILq0NJKvT
gJXUGiurCq0EOruONio8QNLKbSoxzVwBdxXJ63V0LzWx1wLy4SpR63SKO
hRqcXtRDcMIzQTWhJ4BUnsueJW5SXidWy10V_4UINC1sEjlqDRdCox1BZ
uEFZkwmDzRAzCmY7T4W6XozFkigljskei_RfekqW090pe52qunOw'.
sess login.
JWT in an environment value

With the JWT in an environment variable as it is defined as To login with the encoded JWT in an environment variable:

| sess res |
sess := GsTsExternalSession newDefault.
sess parameters 
username: 'GsAdminUser';
setLoginWithJwt;
passwordEnvVar: 'GSAPASS'.
sess login.
JWT in an disk file value

With the JWT in an disk file, as described under To login using the encoded JWT in an file:

| sess res |
sess := GsTsExternalSession newDefault.
sess parameters 
username: 'GsAdminUser';
setLoginWithJwt;
passwordFileName: '/gshost/gemstone/jwtlogin.pass'.
sess login.

Login via GemBuilder for C

Login flags have been to support JWT logins, and logins via other authentication schemes with password in file or environment variable. These flags apply to both classic GCI logins using GciLoginEx() and related methods, and thread-safe GCI logins using GsTsLogin() and related methods.

There is no direct support for login with files containing a JWT in JSON.

See Added login flags for specifics on the new flags.

2.4  JsonWebToken

The class JsonWebToken has been added. Instances of JsonWebToken provide an easier way of manipulating the JSON of a JWT than using JSON itself, and JsonWebToken includes protocol to encode and decode JWTs. JsonWebToken is intended to be a general purpose class to support other JWT use cases.

Use of JsonWebToken is not needed to configure authentication using regularly provided JWT whose public keys are managed by OpenId discovery.

JsonWebToken can be used to create temporary JWTs, with public keys that are manually added to the Stone. This is a convenience for testing JWT login configuration and authentication. Manually added public keys are temporary and not retained over Stone shutdown.

Creating and updating a JsonWebToken

Creating a JsonWebToken

New JsonWebTokens for JWT authentication are created using:

JsonWebToken newForRsa256

While the JsonWebToken class supports JWTs signed using Hash-Based Method Authentication Codes (HMACs) with secret keys, these are not supported for logging into GemStone. See the image for methods supporting non-login related JWTs.

Converting JWTs

JWTs can be converted between the JsonWebToken object format, and the encoded string format or the unencoded JSON. However the JsonWebToken was created, it can be edited as needed, and encoded or re-encoded into a JWT String or printed in JSON.

To create a JsonWebToken from an encoded JWT String:

JsonWebToken fromJwtStrig: aJwtString

To print the JSON for a JsonWebToken:

JsonWebToken asJson

To create an encoded JWT string from a JsonWebToken:

JsonWebToken asJwtString

Updating a JsonWebToken

There are a number of methods to access and update the fields of a JsonWebToken, and several specific test methods.

JsonWebToken >> algorithm, JsonWebToken >> algorithm:
JsonWebToken >> audience, JsonWebToken >> audience:
JsonWebToken >> authorizedParty, JsonWebToken >> authorizedParty:
JsonWebToken >> expirationTime, JsonWebToken >> expirationTime:
JsonWebToken >> issuedAtTime, JsonWebToken >> issuedAtTime:
JsonWebToken >> issuer, JsonWebToken >> issuer:
JsonWebToken >> keyId, JsonWebToken >> keyId:
JsonWebToken >> type, JsonWebToken >> type:
JsonWebToken >> subject, JsonWebToken >> subject:
JsonWebToken >> notBeforeTime, JsonWebToken >> notBeforeTime:
JsonWebToken >> payloadClaimAt:
JsonWebToken >> payloadClaimAt:put:
JsonWebToken >> headerClaimAt:
JsonWebToken >> headerClaimAt:put:
JsonWebToken >> allPayloadClaims 
JsonWebToken >> allHeaderClaims 
JsonWebToken >> allClaims 
JsonWebToken >> secondsUntilExpiration 
JsonWebToken >> isExpired 
JsonWebToken >> isSigned 

After the JsonWebToken is updated, it must be signed with a private key, using:

JsonWebToken >> signWithPrivateKey: privKey.

After it is signed, you should not make any further changes. Updates to the JsonWebToken are ignored after signing.

Using a JsonWebToken to verify login

Normally, JWTs are generated and signed by external services, and validated within GemStone using the public key against an OpenId discovery document. A JsonWebToken is not involved.

For testing, GemStone supports manually adding a public key to the Stone, which allows you to generate a JWT, sign it, and use that JWT for authentication. This allows you to test JWTs that do not exactly match the format generated by your JWT service.

The keys manually added to the Stone are not persistent over Stone restart.

Manual management of public keys in Stone

The following methods support manually adding and removing JWT public keys from the Stone’s set of temporary keys.

System class >> addJwtKey: tlsObj withId: keyIdString
Adds tlsObj to the list of temporary public keys used to authenticate JWT logins. tlsObj must be an instance of either GsX509Certificate or GsTlsPublicKey. Only RSA public keys and certificates are supported. keyIdString must be an instance of String with a length of 1 or more characters. Keys added using this method are temporary and are lost when the Stone is restarted.

System class >> removeJwtKeyWithId: keyIdString
Removes the temporary key with keyIdString from the list of public keys used to authenticate JWT logins. keyIdString must be an instance of String with a length of 1 or more characters. Only keys that were manually added using #addJwtKey:withId: can be removed; keys added by the OID thread cannot be removed.

System class >> removeAllJwtKeys
Removes all temporary keys from the list of public keys used to authenticate JWT logins. Only keys previously added using the #addJwtKey:withId: method are removed. Removing keys not added by the above method is not supported. It is not an error if there are no keys eligible for removal.

System class >> removeJwtKeyWithId: keyIdString ifAbsent: aBlock
Removes the temporary key with keyId from the list of temporary public keys used to authenticate JWT logins. If the key is not found, returns the result of evaluating the zero-argument block aBlock. keyIdString must be an instance of String with a length of 1 or more characters. The key with keyIdString must have been previously added using the #addJwtKey:withId: method. Removing keys not added by the above method is not supported.

Example

The following example demonstrates how to create a JWT and create a corresponding JwtSecurityData, and configure a user to authenticate with that JwtSecurityData.

By manually loading the public key for the key id into the Stone, there is no need for verification of a valid key nor does the server need to be configured for the discovery id.

Example 2.3 Login using JsonWebToken

| kid userName privKey userPro jwt jsd now sess |
 
kid := 'C2C587F2718953AE8A4B37307C1641F3'.
userName := 'GsAdminUser'.
userPro := AllUsers userWithId: userName ifAbsent:[ 
	userPro := AllUsers 
		addNewUserWithId: userName password: 'swordfish'.
	System commit. userPro ].
 
jsd := JwtSecurityData new.
jsd
	addAudience: userName;
	addIssuer:  'https://test.gemtalksystems.com';
	addUserId: userName;
	addUserClaim: (JwtUserClaim 
		newWithJsonKey: #azp 
		jsonKind: #String 
		acceptedValues: { '123456789'}) .
userPro enableJwtAuthenticationWith: jsd.
System commit.
 
privKey := GsTlsPrivateKey 
newFromPemFile: '$GEMSTONE/examples/openssl/private/backup_sign_1_clientkey.pem'
withPassphraseFile: '$GEMSTONE/examples/openssl/private/backup_sign_1_client_passwd.txt'.
 
jwt := JsonWebToken newForRsa256.
jwt audience: userName;
	issuer: 'https://test.gemtalksystems.com';
	keyId: kid;
	authorizedParty: '123456789';
	issuedAtTime: (now := System timeGmt);
	expirationTime: now + 7200; 
	signWithPrivateKey: privKey.
jwt := jwt asJwtString.
 
System addJwtKey: (privKey asPublicKey) withId: kid.
 
sess :=  GsTsExternalSession newDefault .
sess
	username: userPro userId ;
	jwtPassword: jwt.
sess login.
 

You should ensure that the public keys are removed f rom the Stone’s cache when they are no longer needed.

topaz 1> run
	System jwtPublicKeys 
%
[52089345 size:2  Array] anArray
  #1 [52088577 size:32  String] C2C587F2718953AE8A4B37307C1641F3
  #2 [52088065  GsTlsPublicKey] aGsTlsPublicKey
 
topaz 1> run
System removeJwtKeyWithId: 'C2C587F2718953AE8A4B37307C1641F3'.
%

2.5  Login Tracking

The Login Log, if enabled, records JWT successful and failed logins and logouts, as for any login.

The Login Log output now has an additional column providing the specific authentication scheme used for that login.

The options for Login authentication kind are:

0 - GemStone password

1 - UNIX/PAM

2 - LDAP

3 - Kerberos/SSO

4 - X509

5 - JSON Web Token (JWT)

2.6  Debugging

There are many moving parts to a JWT login. Login debugging has been added that reports the specific validations tests and a failure that prevented login, if login failed.

When login debugging is enabled, the JWT validations are printed to stdout; the linked console, or the Gem log file. This allows you to see which validation failed on a login failure.

Following are examples of some of the validations that are printed:

[Debug]: Timestamp is valid
[Debug]: JWT issuer https://accounts.google.com is authorized
[Debug]: Checking user claim sub: valid

Login debugging is printed for all logins, not just JWT logins, but other logins do not provide any further detail. For example:

[Debug]: UserProfile auth kind is: Password (GemStone)
[Debug]: Login failed:  the userId/password combination is invalid or expired.; badPassword

Login debugging can be configured in a number of ways.

Environment variable GS_DEBUG_LOGIN

To help determine the cause of login failures, set the environment variable GS_DEBUG_LOGIN to any value, in the environment of the client. This sets the bit in the login parameters, which can also be done as described in the following sections.

GBS login debugging

To debug JWT logins on GBS, use the GS_DEBUG_LOGIN environment variable.

GCI flags

To enabled login debugging in a GCI login, use the new login flag GCI_LOGIN_DEBUG. SeeAdded login flags for GciLoginEx() and GsTsLogin().

Image methods

When logging in via an external session, configure the GsTsExternalSession or GemStoneParameters to include the debugging flag, using either of the following methods.

GsTsExternalSession >> setLoginDebug
Enables writing debug info to the gem's log file.

GemStoneParameters >> setLoginDebug
Enable writing login debug details to the gem's log file.

Previous chapter