Client Authentication with Private Key JWT using WSO2 Identity Server

Piraveena Paralogarajah
5 min readFeb 15, 2020

--

What is Private Key JWT Client Authentication?

All confidential clients must be authenticated at the Token API to obtain an access token. The platform’s OAuth 2.0 Token API supports the following authentication methods:

  • Basic Authentication (client_secret_basic)
  • Client Secret JWT Authentication (client_secret_jwt)
  • Private Key JWT Client Authentication (private_key_jwt)
  • Mutual TLS Authentication (tls_client_auth)

Let's see how OAuth clients can authenticate to Token API using privat_key_jwt.

All Clients have a private key and public key for the SSL handshake. We can consider this Private Key JWT Authentication in 2 steps:

  1. OAuth2 Client shares its public key with the Authorization Server.
  2. OAuth2 client sends the JWT data signed with its private key to the token API.
  3. Authorization Server extracts the signature and authenticates the client

See these steps in detail below:

  1. OAuth2 Client shares its public key with the Authorization Server.
OAuth2 client shares its public key with Authorization Server

2. OAuth2 client sends the JWT data signed with its private key to the token API.

OAuth2 client sends the JWT data signed with its private key to the token endpoint to authneticates itself and obtain access token

3. Authorization Server extracts the signature and authenticates the client

The authorization server authenticates the client by Validateingthe signature using the public key of the client

Configuring Client Authentication with Private Key JWT

Note:

Before start this step, Create an OAuth Service provider(SP) in WSO2 Identity Server and obtains the client-id of the SP. You can follow this doc

  1. Client-side

Generate a private key and public key for the client.

Replace <client_ID> with the client-id of the OAuth Service provider

keytool -genkey -alias <client_ID> -keyalg RSA -keystore TodayApp.jkskeytool -export -alias <client_ID> -file nwU59qy9AsDqftmwLcfmkvOhvuYa -keystore TodayApp.jkskeytool -importkeystore -srckeystore TodayApp.jks -destkeystore TodayApp.p12 -deststoretype PKCS12openssl pkcs12 -in TodayApp.p12 -nokeys -out pubcert.pemopenssl pkcs12 -in TodayApp.p12 -nodes -nocerts -out privatekey.pem

Here the CN you enter is important for next steps. I have added “piraveena” as my CN in the certificate.

You will be getting quite a few files once you finish above steps. ie executing above commands. Below is a list.

  1. TodayApp.jks -keystore which contains keys
  2. <client_ID> -Public certificate
  3. pubcert.pem-public key in pem format
  4. privatekey.pem-private key.

When you check the privatekey.pem file,

Create the Client Assertion

the client must send the request that contains the following parameters to the token endpoint

  • client_assertion_type : a type of client_assertion
  • client_assertion: a JWT that contains information for client authentication

The value of the client_assertion_type must be “urn:ietf:params:oauth:client-assertion-type:jwt-bearer”.

The value of the client_assertion must be a signed JWT that contains information for client authentication and meet the following requirements.

<Signing>

  • The JWT must be signed
  • A public key used for signature verification must be registered at the authorization server.

<Payload>
The JWT must contain the REQUIRED claims listed below

iss: [REQUIRED] Issuer. This must contain the client_id of the OAuth Client.sub: [REQUIRED] Subject. This must contain the client_id of the OAuth Client.aud: [REQUIRED] Audience. The aud (audience) Claim. A value that identifies the Authorization Server as an intended audience. The Authorization Server must verify that it is an intended audience for the token. The Audience should be the URL of the Authorization Server’s Token Endpoint.jti: [REQUIRED] JWT ID. A unique identifier for the token, which can be used to prevent reuse of the token. These tokens must only be used once unless conditions for reuse were negotiated between the parties; any such negotiation is beyond the scope of this specification.exp:[REQUIRED] Expiration time on or after which the JWT must not be accepted for processing.iat:[OPTIONAL] Time at which the JWT was issued.

The Decoded JWT will be as below:

{
“typ”: “JWT”,
“alg”: “RS256”,
“kid”: “piraveena”
}
{
“iss”: “CAeFC1u5I0SZvh5FfYgYk2IMRNsa”,
“sub”: “CAeFC1u5I0SZvh5FfYgYk2IMRNsa”,
“exp”: 1581752233,
“iat”: 1581748633,
“jti”: “10003”,
“aud”: “https://localhost:9443/oauth2/token"
}
  • In this decoded JWT, change the “iss”, “sub” and “kid” value. You can use https://jwt.io/ and construct your own JWT.
  • Change “iat” and “exp” based on your current time. “exp” time should be greater than the current time
  • Also you need to copy and paste the content of your private key also in the page. (Starting from “ — — -BEGIN PRIVATE KEY — — -” and ended with “ — — -END PRIVATE KEY — — -”). At the left side you can observe the signed JWT

Now the client is ready to use the private-key JWT for cleitn authentication!

2. Identity Server Side

  • Import the public key of the client into the truststore of wso2 identity server
keytool -importcert -alias <client-id> -file pubcert.pem -keystore ${IS_HOME}//Users/piraveena/Documents/issue/wso2is-5.10.0/repository/resources/security/client-truststore.jks -storepass wso2carbon -noprompt

Note:

deployment.toml configs used here are applicable with IS 5.9.0 onwards. To know the configs prior to IS5.9.0, please refer to this official doc

  • Add the following configs in deployment.toml file located in <IS_HOME>/repository/conf folder
[[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 = true
EnableCacheForJTI = true
[oauth.grant_type.client_credentials]
allow_id_token = true # to get id_token for client_credential type
[[cache.manager]]
name="PrivateKeyJWT"
timeout="300"
capacity=”5000"
isDistributed=”false”
  • Restart the server.
  • Open the management console
  • Navigate the Service provider and upload the service provider’s public certificate via the management console.

Curl commands to get id_token with private-key JWT authentication

Use the below cURL to retrieve the access token and refresh token using a JWT.

For Authz_code grant type

Authz_code grant type: Replace <authorization-code> and <private_key_jwt> in below curl

curl -v POST -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=authorization_code&code=f2d0f7dd-df6d-34ac-9d61-851f4f0cab9f&scope=openid&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=<jwt_assertion>&redirect_uri=http://localhost:8080/playground2/oauth2client" https://localhost:9443/oauth2/token

For information on how to get the authorization-code, check Try Authorization Code Grant page.

For client credential grant type (to get id_token for client-credential grant type, you have to enable in wso2 identity server. By default IS won’t send for client-credential grant type):

curl -v POST -H "Content-Type: application/x-www-form-urlencoded;charset=ISO-8859-1" -k -d "grant_type=client_credentials&scope=openid&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=<jwt_assertion>&redirect_uri=http://localhost:8080/playground2/oauth2client" https://localhost:9443/oauth2/token

--

--

Piraveena Paralogarajah

Software Engineer @WSO2, CSE Undergraduate @ University of Moratuwa, Former Software Engineering Intern @ WSO2