Private Key JWT authentication in WSO2 Identity Server

Kayathiri Mahendrakumaran
Identity Beyond Borders
6 min readFeb 8, 2023

Private Key JWT is a method of client authentication where the client creates and signs a JWT using its own private key.

Confidential clients use an assertion to authenticate the authorization
server’s token endpoint.

Why authenticate OAuth 2.0 clients?

Client authentication ensures the tokens get issued to a legitimate client and not some other, potentially malicious, party.

Public clients, which don’t get authenticated, may for this reason only get issued with tokens that have a restricted scope.

Types of client Credential

  • Shared secret — The server issues the client with a secret (password) that is stored by the server and the client.
  • Private key — The client generates a private RSA key and stores it securely. The client then authenticates by signing an assertion with the private key.

Of the two methods, the one based on a private key has stronger security properties.

Client Authentication Methods

Shared Secret-based

  1. client_secret_basic — This is essentially basic authentication. The server will generate a client_id and client_secret during client registration.
    The client authentication is passed in the Authorization HTTP header.
POST /token HTTP/1.1
Host: wso2.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

grant_type=authorization_code
&code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4

2. client_secret_post

It differs from client_secret_basic in that the credentials are passed in the request body as form parameters.

POST /token HTTP/1.1
Host: wso2.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4
&client_id=s6BhdRkqt3
&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw

3. client_secret_jwt

Also uses a client secret, but this time the secret is used as a key to compute a hash-based message authentication code (HMAC) for a JWT assertion.

The client prepares data as per the specifications and generated the signature based on the secret generated by the server. This can be verified by the server with the secret.

Private Key based

  1. private_key_jwt

Here, the client will prepare a pair of private keys and public keys. Similar to the client_secret_jwt the client will prepare the data with the claims and the signature will be generated with the private key. And the server will use the public key to verify the data. This is similar to asymmetric key encryption.

2. tls_client_auth — Mutual TLS authentication

Let’s see how to configure the private_key_jwt mechanism with Identity Server

01 — Setup Authorization Server.

  1. In this demo, we use wso2is-6.0.0 as the authorization server. You can download the zip file here.
  2. Download the private key JWT authenticator connector from IS connector store
  3. Add the connector to<IS_HOME>/repository/component/dropins the directory.
  4. To register the JWT grant type, add the following configuration to <IS_HOME>/repository/conf/deployment.toml file.
[[event_listener]]
id = "private_key_jwt_authenticator"
type = "org.wso2.carbon.identity.core.handler.AbstractIdentityHandler"
name = "org.wso2.carbon.identity.oauth2.token.handler.clientauth.jwt.PrivateKeyJWTClientAuthenticator"
order = "899"[event_listener.properties]
PreventTokenReuse= false
RejectBeforeInMinutes= "100"
TokenEndpointAlias= "https://localhost:9443/oauth2/token"
# The cache configuration is needed because when too many calls are made to the database there can be a performance impact.
# To reduce this impact, the cache configuration is done so that the information is read from the cache instead of the database.
[[cache.manager]]
name="PrivateKeyJWT"
timeout="300"
capacity="5000"
isDistributed="false"

4. Start the identity server

02 — Configure Service Provider

  1. Log in to the management console
  2. Navigate to Service Providers -> Add
  3. Provide a name for your application and register.
  4. Navigate to the Inbound Authentication Configuration -> OAuth/OpenID Connect Configuration and click configure.
  5. Provide the callback URL of your application.

Note-> I’ll use the https://oidcdebugger.com/ as a sample application. The callback url -> https://oidcdebugger.com/debug

After configuring successfully, the server will generate a client ID and secret for your client application.

03 — Generate private key and public key for the client app.

  1. Open a terminal and execute the following command to create the client keystore.
keytool -genkey -alias <client_ID> -keyalg RSA -keystore wso2keyStore.jks

2. Export the public certificate from Keystore. (Output file: <client_ID>)

keytool -export -alias <client_ID> -file <client_ID> -keystore wso2keyStore.jks

3. Next we need to extract the public key and private key in .pem format.

Convert .jks keystore to PKCS#12 format. Output wsoskeystore.p12 keystore.

keytool -importkeystore -srckeystore wso2keyStore.jks -destkeystore wso2keyStore.p12 -deststoretype PKCS12

Then, export the public key from the .p12 keystore. This will generate the pubcert.pem file.

openssl pkcs12 -in wso2keyStore.p12 -nokeys -out pubcert.pem

Then, export the private key from the .p12 keystore. This will generate the privatekey.pem file.

openssl pkcs12 -in wso2keyStore.p12 -nodes -nocerts -out privatekey.pem
Generated files

04. Make the public key accessible to the Identity server.

You can use the JWKS endpoint or upload the certificate. Here I will add the SP certificate.

  1. Open pubcert.pem and copy the section from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE----- and paste it in the input box under upload SP certificate option.

05 — Prepare JSON data

Refer OpenId-clinet authetication to understand the data format.

JWT header:

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

JWT payload:

Here is my payload. (You can use this link to generate the exp & iat)

{
"iss": "voSDEZAMkq3Mrf2k3caRNIdqIfYa",
"sub": "voSDEZAMkq3Mrf2k3caRNIdqIfYa",
"exp": 1675767965,
"iat": 1675746365,
"jti": "108003",
"aud": "https://localhost:9443/oauth2/token"
}
  1. Go to https://jwt.io/ and copy and paste your header and payload in the JWT Decoded section.

2. Copy the — — -BEGIN PRIVATE KEY — — — to — — -END PRIVATE KEY — — — section of privatekey.pemand paste the content of your private key in the VERIFY SIGNATURE part of Decoded section.

3. Now you can copy the signed JWT in the Encoded section.

This is the client_assetion .

Now we can send the token request and verify.

06 — Send the token request

curl --location --request POST 'https://localhost:9443/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=openid' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion=<jwt_assertion>' \
--data-urlencode 'redirect_uri=<call_back_url>'

Replace <jwt_assertion> and <call_back_url> with yours.

You will get a token response if the client authentication is success.

{
"access_token":"ccbaa39f-7d4f-3efa-aa9a-ecfac5a8be3c",
"token_type":"Bearer",
"expires_in":3542
}

Now, we have successfully completed client authentication with Private Key JWT Client Authentication.

For further understanding, you can watch the following episode in #identity15.

--

--

Kayathiri Mahendrakumaran
Identity Beyond Borders

Senior Software Engineer 👨‍💻, WSO2 | Undergraduate👩‍🎓 , Computer Science & Engineering | Writer ✍️