Private Key JWT authentication in WSO2 Identity Server
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
- client_secret_basic — This is essentially basic authentication. The server will generate a
client_id
andclient_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
- 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.
- In this demo, we use wso2is-6.0.0 as the authorization server. You can download the zip file here.
- Download the private key JWT authenticator connector from IS connector store
- Add the connector to
<IS_HOME>/repository/component/dropins
the directory. - 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
- Log in to the management console
- Navigate to Service Providers -> Add
- Provide a name for your application and register.
- Navigate to the
Inbound Authentication Configuration -> OAuth/OpenID Connect Configuration
and click configure. - 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.
- 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
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.
- 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"
}
- 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.pem
and 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.