Credential:  
None
  • None
  • Manage Credentials
Launch IDE ↗

Stitch SSO

5.6. Confidential Clients

If you're reading this, you should have by now already gone through the introductory docs which described how to integrate Stitch SSO using a public client.

This document explains how to integrate Stitch SSO using a confidential client. For the purposes of Stitch SSO, a confidential client could most succinctly be described as the addition of client authentication (as opposed to just user authentication) to the authorization flow.

The exact form of this client authentication 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.

Use of a confidential client comes with some security benefits, but also new risks.

The first additional risk is that user access and refresh tokens are now stored in a centralised location (on your server). Should a breach occur, one in which both the Stitch provided client certificate and user tokens are divulged, an attacker will be able to impersonate the client and gain access to the user's accounts. To help mitigate this risk, it is important that clients have sufficient monitoring in place, so that Stitch engineers can promptly revoke the client certificate, limiting the impact of the breach.

A second risk is more of a data protection risk. Financial information is frequently of an intensely personal quality. It may be tempting to store this information for later analysis, or because there is perceived business value to amassing a large database of financial data. However, this presents a tempting target for attackers, and introduce new compliance requirements. If it's possible, it is better to only retain the data needed for operational purposes and limit the time in which this operational data is stored.

To complete this guide, please ensure that you have received a confidential 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.

Note

Your certificate has an expiry date. Please contact Stitch before the expiry date to request a new certificate

Differences between Public and Private Flows

PKCE flow with private key jwt

As can be seen in the sequence diagram above, the confidential flow splits the client application into two cooperating entities, the backend and frontend.

Typically, in this flow the frontend would trigger some action, such as clicking a login button. This would result in a request being sent to the back end. The backend would generate the Stitch SSO redirect URL, per the standard getting started docs. The code_verifier will need to be persisted in a temporary operational datastore. It is highly recommended that the state parameter be used as the key for storing this value, as this helps mitigate cross site request forgery.

When the user has completed authentication and authorization, they'll be redirected back to the application's redirect_uri. This redirect_uri could either redirect to the client backend, conveying the authorization code to the backend in the process, or redirect back to the frontend. For the latter case, the frontend will need to transmit the resultant authorization code via a secure backchannel. Once this has occurred, the frontend's role in the authorization process ends.

Calling the /connect/token endpoint is where the flows substantially differ. Confidential clients add two new required parameters, client_assertion_type and client_assertion as per RFC 7523. These two parameters are used to validate that the token request originates from the client. For this security measure to be effective, the protection and careful handling of the client_certificate is necessary. A production client_certificate should never be checked into source control in unencrypted form, and instead be stored in a credential management system such as Hashicorp Vault, Azure Key Vault, or AWS Secrets Manager.

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
ParameterDescription
audShould always have the value https://secure.stitch.money/connect/token
issThe client_id is the issuer of this token
subThe client_id is the subject of this token
jtiThe JWT Token Identifier MUST be a unique randomly generated identifier and MAY NOT be reused between requests
iatTime at which the JWT was issued (in Unix time, seconds)
expExpiration 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
nbfThe 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. Not 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:

java-jwt-sample.zip

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.public
2openssl pkey -in ./cert.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();
3
4String 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 time
8Date issuedAt = Date.from(currentInstant);
9Date notBefore = issuedAt;
10Date expiresAt = Date.from(currentInstant.plusSeconds(60)); // Should be a small value after now
11
12String 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 Sample

This section demonstrates how to generate a client JWT suitable for retrieving an access token from Stitch SSO. Not that this JWT is single use only. The example uses Dotnet Core 3.1 and makes use of the System.IdentityModel.Tokens.Jwt library. The full source code for this example can be downloaded here:

csharp-jwt-sample.zip

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 commands.

1openssl pkcs12 -export -out certificate.pfx -inkey cert.pem -in certificate.pem

Generating the Token

The first thing to do is to load in the generated pfx. You can do this using the following code:

1var cert = new X509Certificate2(File.ReadAllBytes("certificate.pfx"));

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";
3
4var 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 time
8var 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.RsaSha256
15 )
16);
17
18var 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 -v $PWD/certificate.public:/app/certificate.public -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:

1dotnet run $CLIENT_ID
NodeJS Sample

This section demonstrates how to generate a client JWT suitable for retrieving an access token from Stitch SSO. Not 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:

nodejs-jwt-sample.zip

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");
6
7const 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 shorter
15 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.

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 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.

 unknown

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.

Using the private_key_jwt in token calls

Once you're able to generate a JWT, you're almost ready to retrieve an access tokens and make use of refresh tokens. Token calls have the same format as token calls using public clients, however require two additional values as described in the table below:

Required JWT Parameters
ParameterDescription
client_assertion_typeShould always have the value urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertionThe value of the generated private_key_jwt

These two additional parameters are required for both authorization_code and refresh_token grant types, and more generally for any calls made to the token endpoint.