Skip to content

Instantly share code, notes, and snippets.

@fsans
Last active January 2, 2024 13:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fsans/6c88cb755837c22b265f31d9aa8fdc23 to your computer and use it in GitHub Desktop.
Save fsans/6c88cb755837c22b265f31d9aa8fdc23 to your computer and use it in GitHub Desktop.
Using server to server Oauth 2 JWT authentication within FileMaker. Contains a few FileMaker CF and implementation.

Server to Server JWT from FileMaker

JWT Anatomy

Using OAuth 2.0 for Server to Server Applications (Service Accounts)

General Flow

Creating the JWT token...

JWT Composition

A JWT is composed as follows:

{Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}

The base string for the signature is as follows:

{Base64url encoded header}.{Base64url encoded claim set}

JWT header

signing algorithm and the format of the assertion. In this case: RSA SHA-256 and JWT token format

{"alg":"RS256","typ":"JWT"}
JWT claim set

params are:

Name Description
iss The email address of the service account
scope A space-delimited list of the permissions that the application requests
aud A descriptor of the intended target of the assertion. When making an access token request this value is always https://oauth2.googleapis.com/token
exp The expiration time of the assertion, specified as seconds since 00:00:00 UTC
iat The time the assertion was issued, specified as seconds since 00:00:00 UTC, January 1, 1970
{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "scope": "https://www.googleapis.com/auth/devstorage.read_only",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
additional claims

To obtain an access token that grants an application delegated access to a resource, include the email address of the user in the JWT claim set as the value of the sub field.

Name Description
sub The email address of the user for which the application is requesting delegated access
{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "sub": "some.user@example.com",
  "scope": "https://www.googleapis.com/auth/prediction",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
encoding claim set

The JWT claim set should be serialized to UTF-8 and Base64url-safe encoded

The input for the signature is the byte array of the following content:

{Base64url encoded header}.{Base64url encoded claim set}

The only signing algorithm supported by the Google OAuth 2.0 Authorization Server is RSA using SHA-256 hashing algorithm. This is expressed as RS256 in the alg field in the JWT header.

Sign the UTF-8 representation of the input using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key obtained from the Google API Console. The output will be a byte array.

Script

Custom functions

GenerateJWT( )

Usage

GenerateJWT( iss; scope; aud; pk )

Sample
GenerateJWT(
	"YOUR_SERVICE_ACCOUNT_EMAIL";
	"https://www.googleapis.com/auth/devstorage.read_only";
	"https://oauth2.googleapis.com/token";
	"YOUR_PRIVATE_KEY"
)
Dependendencies
(CF) UnixTime()
(CF) Base64urlEncode(str)
ClaimSet parameters
  • iss: The email address of the service account
  • scope: A space-delimited list of the permissions that the application requests
  • aud: A descriptor of the intended target of the assertion. When making an access token request this value is always https://oauth2.googleapis.com/token
  • exp: The expiration time of the assertion, specified as seconds since 00:00:00 UTC
  • iat: The time the assertion was issued, specified as seconds since 00:00:00 UTC, January 1, 1970
  • sub: The email address of the user for which the application is requesting delegated access (only used for delegated access)
Let(
	[
		_ISS = iss;
		_SCOPE = scope;
		_AUD = aud;
		_PRIVATE_KEY = pk;
		_TOKEN_TTL = 3600;
		_UTC_ZERO = UnixTime();

		header = JSONSetElement ( "{}";
				["alg"; "RS256"; JSONString]; 
				["typ"; "JWT"; JSONString] );

		claimSet = JSONSetElement ( "{}";
				["iss"; _ISS; JSONString]; 
				["scope"; _SCOPE; JSONString]; 
				["aud"; _AUD; JSONString]; 
				["iat"; _UTC_ZERO ;  JSONNumber];
				["exp";  _UTC_ZERO + _TOKEN_TTL;  JSONNumber] );

		signatureData = "" & Base64urlEncode ( header ) & "." & Base64urlEncode ( claimSet ) & "";

		signature = CryptGenerateSignature ( signatureData ; "SHA256" ; _PRIVATE_KEY ; "" );

		signedJWTData = signatureData & "." & Base64urlEncode (signature )
	];

	signedJWTData

)

UnixTime( )

Just returns the seconds since 00:00:00 UTC

Floor( GetAsNumber ( Get ( CurrentTimeUTCMilliseconds ) ) / 1000 ) - GetAsNumber ( Timestamp ( "01/01/1970" ; "00:00:00" ) )

Base64urlEncode( )

Does a "url safe" Base64 Encoding, accordingly to RFC4648 https://tools.ietf.org/html/rfc4648 Do not use simple Base64 native encodings from FileMaker.

Let (
	_e=Base64EncodeRFC(4648 ;data );
	Substitute( _e;["+";"-"];["/";"_"];["\r";""];["\n";""];["=";""] )
 )
 

Get the Token

Basicaly pass the JWT body to the authentication server as:

$ASSERTION = GenerateJWT( iss; scope; aud; pk )
$GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"

Post a CURL to the token_uri: https://oauth2.googleapis.com/token with the cURL options:

CURL -X POST -d "grant_type=$GRANT_TYPE&assertion=$ASSERTION"

The result contains your authorization token, expiration date time or error messages if request fails.

@sarahdatasmart
Copy link

Hi, your explanation is what I need for a JWT signature that I have to establish.
One question: the Base64urlEncode function is depreciated in FileMaker 18 and 19 --> do I use instead the Base64EncodeRFC(4648 ;data ) as you described? (when I use the debugger on jwt.io it keep mentioning that the signature is wrong)

@fsans
Copy link
Author

fsans commented Jun 7, 2021

Base64urlEncode is a FileMaker Custom Function that does "Url safe" Base64 encoding accordingly to RFC4648. Not a FileMaker native function. Do not use the native Base64EncodeRFC(), build the Custom Function and use it.

@fsans
Copy link
Author

fsans commented Feb 3, 2023 via email

@fsans
Copy link
Author

fsans commented Feb 3, 2023

This CFunction is not affected by the SHA algorithm deprecation on FMPro 19.x accordingly to this release notes

This has been checked against FMPro 19.6.3.302 by FEB.03.2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment