Stitch SSO
4.7. Generating a Client Assertion
This document explains how to generate a client assertion. A client assertion could most succinctly be described as a means of client authentication, used in a number of our authorization flows.
The exact form of this client assertion is through a mechanism known as a private_key_jwt
client assertion, as described in Section 2.2 of RFC 7523 and Section 9 of OpenID Connect Core 1.0.
private_key_jwt
is one of the three client authentication mechanisms permitted by the OpenID FAPI Read Only Profile.
To complete this guide, please ensure that you have received a client_id
, and a client_certificate
. The certificate contains both the public and private keys and is needed to generate a client JWT. If you need help with the above, please reach out to a Stitch engineer via the usual support channels on Slack or our Support Form.
Note
Your certificate has an expiry date. Please contact Stitch before the expiry date to request a new certificate
Using the Client Certificate to generate a private_key_jwt
The certificate provided to you is encoded in the PEM format and contains both the public and private parts of the RSA certificate required to generate the private_key_jwt
. Depending on the programming language and operating system used, you may need to convert this certificate into different formats. We recommend OpenSSL for this purpose.
Once you're able to correctly parse the certificate, you'll need to create a single use JWT, containing the following parameters, and sign the algorithm using the RS256 (RSA 256) algorithm:
Required JWT Parameters | |
---|---|
Parameter | Description |
aud | Should always have the value https://secure.stitch.money/connect/token |
iss | The client_id is the issuer of this token |
sub | The client_id is the subject of this token |
jti | The JWT Token Identifier MUST be a unique randomly generated identifier and MAY NOT be reused between requests |
iat | Time at which the JWT was issued (in Unix time, seconds) |
exp | Expiration time (in Unix time, seconds) on or after which the ID Token MUST NOT be accepted for processing. Should ideally be quite close to the issuance time |
nbf | The token is invalid before this time. Should be quite close to now, or be slightly in the past |
The jwt.io website lists a set of libraries that can be used to create, sign, or verify tokens for many different platforms. When choosing a library, please ensure that the capability list includes support for the RS256 algorithm and includes Signing in its feature set.
Sample Code
The following samples illustrate how to generate a client jwt for a few different platforms. If yours is not listed here, the process should be much the same
Java Sample
This section demonstrates how to generate a client JWT suitable for retrieving an access token from Stitch SSO. Note that this JWT is single use only. The example uses Java and makes use of the Auth0 Java JWT library. The full source code for this example can be downloaded here:
Converting your certificate
For this example to work, we'll need to use OpenSSL to place the private key into a separate file, and extract the public key from the certificate. Assuming your certificate has the name certificate.pem
, this may be done using the following commands:
1openssl x509 -pubkey -noout -in certificate.pem > certificate.public2openssl pkey -in certificate.pem > certificate.private
Generating the Token
Now that we have the keys in the correct format, we'll use the provided PemUtils
class to read the private and public keys into the application:
1RSAPublicKey publicKey = (RSAPublicKey) PemUtils.readPublicKeyFromFile("certificate.public", "RSA");2RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile("certificate.private", "RSA");
We want to sign the token using the RSA 256 algorithm, so need to create an instance of this algorithm:
1Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
Finally, we'll want to define the token values and then actually generate the token:
1String clientId = args.length >= 1 ? args[0] : "CLIENT_ID";2Instant currentInstant = Instant.now();34String audience = "https://secure.stitch.money/connect/token";5String issuer = clientId;6String subject = clientId;7String jti = UUID.randomUUID().toString(); // Needs to be a unique value each time8Date issuedAt = Date.from(currentInstant);9Date notBefore = issuedAt;10Date expiresAt = Date.from(currentInstant.plusSeconds(60)); // Should be a small value after now1112String clientJwt = JWT.create()13 .withAudience(audience)14 .withIssuer(issuer)15 .withSubject(subject)16 .withJWTId(jti)17 .withIssuedAt(issuedAt)18 .withNotBefore(notBefore)19 .withExpiresAt(expiresAt)20 .sign(algorithmRS);
Running the Sample
If you have Docker installed, you can run the sample using the following command, assuming you placed certificate.public
and certificate.private
in the root directory of the sample
1docker build -t hello-java-jwt .2docker run -it --rm -v $PWD/certificate.public:/app/certificate.public -v $PWD/certificate.private:/app/certificate.private hello-java-jwt $CLIENT_ID
If your development environment is configured to be able to run Java applications, this can also be tested out by calling:
1./gradlew run --args=$CLIENT_ID
.NET Core 3.1 Sample
This section demonstrates how to generate a client JWT suitable for retrieving an access token from Stitch SSO. Note that this JWT is single use only. The example uses .NET Core 3.1 and makes use of the System.IdentityModel.Tokens.Jwt
library. The full source code for this example can be downloaded here:
Converting your certificate
For this example to work, we'll need to use OpenSSL to convert the PEM file into a PFX file. PFX is the preferred format for certificates in the .NET ecosystem. Assuming your certificate has the name certificate.pem
, this conversion may be performed using the following command. You will be prompted to set a password for the PFX file, please keep this for later use.
1openssl pkcs12 -export -out certificate.pfx -inkey certificate.pem -in certificate.pem
Generating the Token
The first thing to do is read the password from an environment variable and then load in the generated PFX. You can do this using the following code:
1var password = Environment.GetEnvironmentVariable("CERT_PASSWORD");2var cert = new X509Certificate2(File.ReadAllBytes("certificate.pfx"), password);
After this, it's simply a matter of defining the token parameters
1var now = DateTime.UtcNow;2var clientId = args.Length >= 1 ? args[0] : "CLIENT_ID";34var audience = "https://secure.stitch.money/connect/token";5var issuer = clientId;6var subject = clientId;7var jti = Guid.NewGuid().ToString(); // Needs to be a unique value each time8var issuedAt = now.ToEpochTime().ToString();9var notBefore = now;10var expiresAt = now + TimeSpan.FromSeconds(60); // Should be a small value after now
And then generating the token:
1var token = new JwtSecurityToken(2 issuer,3 audience,4 new[]5 {6 new Claim(JwtClaimTypes.JwtId, jti),7 new Claim(JwtClaimTypes.Subject, subject),8 new Claim(JwtClaimTypes.IssuedAt, issuedAt, ClaimValueTypes.Integer64),9 },10 notBefore,11 expiresAt,12 new SigningCredentials(13 new X509SecurityKey(cert),14 SecurityAlgorithms.RsaSha25615 )16);1718var tokenHandler = new JwtSecurityTokenHandler();
Running the Sample
If you have Docker installed, you can run the sample using the following command, assuming you placed certificate.pfx
in the root directory of the sample
1docker build -t hello-csharp-jwt .2docker run -it --rm --env CERT_PASSWORD=$YOUR_PFX_PASSWORD -v $PWD/certificate.pfx:/app/certificate.pfx hello-csharp-jwt $CLIENT_ID
If your development environment is already configured to be able to run dotnet core applications, this can also be tested out by calling:
1CERT_PASSWORD="$YOUR_PFX_PASSWORD" dotnet run $CLIENT_ID
.NET 5 Sample
The .NET 5 code for generating a client JWT using a PFX file is the same as the .NET 3.1 sample above, but .NET 5 also has better support for PEM files. The full source code for this example can be downloaded here:
Generating the Token (PEM certificate)
The code for generating the token is the same as the .NET Core 3.1 sample above with one small difference when loading the certificate, assuming your certificate is named certificate.pem
:
1var cert = X509Certificate2.CreateFromPemFile("certificate.pem");
Running the Sample
If you have Docker installed, you can run the sample using the following command, assuming you placed certificate.pem
in the root directory of the sample
1docker build -t hello-csharp-5-jwt .2docker run -it --rm -v $PWD/certificate.pem:/app/certificate.pem hello-csharp-5-jwt $CLIENT_ID
If your development environment is already configured to be able to run .NET 5 applications, this can also be tested out by calling:
1dotnet run $CLIENT_ID
NodeJS Sample
This section demonstrates how to generate a client JWT suitable for retrieving an access token from Stitch SSO. Note that this JWT is single use only. The example has been tested on Node v12.0 and makes use of the jsonwebtoken library. The full source code for this example can be downloaded here:
Generating the Token
The first thing to do is to load in the certificate. The jsonwebtoken library natively supports the PEM format, so no conversion is necessary.
1const fs = require('fs');2const pemCert = fs.readFileSync().toString('utf-8');
After this we'll need to set the various parameters required to sign the JWT:
1const issuer = clientId;2const subject = clientId;3const audience = 'https://secure.stitch.money/connect/token';4const keyid = getKeyId(pemCert);5const jwtid = crypto.randomBytes(16).toString("hex");67const options = {8 keyid,9 jwtid,10 notBefore: "0",11 issuer,12 subject,13 audience,14 expiresIn: "5m", // For this example this value is set to 5 minutes, but for machine usage should generally be a lot shorter15 algorithm: "RS256"16};
This gives us enough information to finally sign the token:
1const token = jwt.sign({}, pemCert, options);
One wrinkle that we glossed over above was retrieving the key id from the PEM
certificate. If you open the file up, you'll see that it conforms to a simple
textual format that contains two rows with the key, localKeyId
.
While we could probably install a library to extract this value, for the purposes of this sample, we'll just grab the value using a regex or two:
1function getKeyId(cert) {2 const lines = cert.split('\n').filter(x => x.includes('localKeyID:'))[0];3 const result = lines.replace('localKeyID:', '').replace(/\W/g, '');4 return result;5}
This id could also be manually extracted.
Running the Sample
To run the sample, make sure that you've got at least Node 12 installed, then run
npm install
in the sample root directory.
After this you may call the following command, replacing $CLIENT_ID
with your
client id and $PATH_TO_PEM_CERT
with the location of your certificate on disk:
1npm run generate-jwt -- $CLIENT_ID $PATH_TO_PEM_CERT
The console output of this command should output a valid JWT.
Python Sample
This section demonstrates how to generate a client JWT suitable for retrieving client tokens as well as user access tokens from the Stitch SSO. The example makes use of the pyjwt library.
Generating the Token
1import jwt2import time3import uuid45with open("./client-certificate.pem", "r") as cert:6 secret = cert.read()78 now = int(time.time())9 one_hour_from_now = now + 36001011 payload = {12 "aud": "https://secure.stitch.money/connect/token",13 "iss": "CLIENT_ID",14 "sub": "CLIENT_ID",15 "jti": str(uuid.uuid4()),16 "iat": now,17 "nbf": now,18 "exp": one_hour_from_now19 }20 encoded_jwt = jwt.encode(payload, secret, algorithm="RS256")2122 print(encoded_jwt)
Verifying that the token is correct
If your token correctly generated, it should look something like this:
1eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjgwNDE2MDQ5QzE0MjZDNDI0MzFGOERDMEUyRjUyQ0JEMDM1RjU1RUMifQ.eyJpYXQiOjE1ODgxNzgxODAsIm5iZiI6MTU4ODE3ODE4MCwiZXhwIjoxNTg4MTc4NDgwLCJhdWQiOiJodHRwczovL3NlY3VyZS5zdGl0Y2gubW9uZXkvY29ubmVjdC90b2tlbiIsImlzcyI6ImFiYyIsInN1YiI6ImFiYyIsImp0aSI6IjdhZTgwNDhhNDRlM2JlNjA5NjAxMjkzYzM0YWYyYmY2In0.E3AKcnQinvcEpmcIdE6mLi2Wem235HzQ7ig1HnHilcHr-h7UP_nbLT0AyR1zFUCnXQygdRFPAOEI7uk8khnjaUIdiI6CIFekMRxXNRBREOO1wJB_QAr5P-wRQiWG0o7nCCYcpD0DYMDtIVW99b_QlnQnXqz_11HVv1DlBQYCU8GiRxFvoSkuIsOEGIm3Ie418Lp4nQTM7cT4-LoN5OxiybxHG-V5C1ASA0T38z9ozgxg27IJ1GaLg8RG5gxo8oI9bL_URvQsvldsROQjGQ6DmHb0nNM6Bn1p2ZEpusMig5AHANJ8_bXc25umd_f-xXrBzb1M_L3OC_feQGx-Q1gTXA
Pasting this token value into the widget below should produce header and payload values that look like the following:
1{2 "header": {3 "alg": "RS256",4 "typ": "JWT",5 "kid": "80416049C1426C42431F8DC0E2F52CBD035F55EC"6 },7 "payload": {8 "iat": 1588178180,9 "nbf": 1588178180,10 "exp": 1588178480,11 "aud": "https://secure.stitch.money/connect/token",12 "iss": "abc",13 "sub": "abc",14 "jti": "7ae8048a44e3be609601293c34af2bf6"15 },16 "signature": "E3AKcnQinvcEpmcIdE6mLi2Wem235HzQ7ig1HnHilcHr-h7UP_nbLT0AyR1zFUCnXQygdRFPAOEI7uk8khnjaUIdiI6CIFekMRxXNRBREOO1wJB_QAr5P-wRQiWG0o7nCCYcpD0DYMDtIVW99b_QlnQnXqz_11HVv1DlBQYCU8GiRxFvoSkuIsOEGIm3Ie418Lp4nQTM7cT4-LoN5OxiybxHG-V5C1ASA0T38z9ozgxg27IJ1GaLg8RG5gxo8oI9bL_URvQsvldsROQjGQ6DmHb0nNM6Bn1p2ZEpusMig5AHANJ8_bXc25umd_f-xXrBzb1M_L3OC_feQGx-Q1gTXA"17}
To check whether the JWT was signed correctly, paste the JWT into the token tab, and the provided PEM file into the certificate portion of this widget.
If correctly signed, the status indicator in the top right of the widget should change to valid.
This check in the widget above is performed in the browser and thus your certificate or token is not transmitted remotely. In general, you should never divulge your certificate to any third party, no matter how seemingly trustworthy they appear. If a certificate has been divulged to a third party, however innocuous this may seem, please contact Stitch so that we can issue you a new client certificate.