Private Key JWT Client Authentication in WSO2 Identity Server

  1. Confidential Client — client implemented on a secure server with
    restricted access to the client credentials.
  2. Public Client — Clients executing on the device used by the
    resource owner.

OAuth 2.0 Token API supports the following client authentication methods:

  1. client_secret_basic — Include client ID and client secret for Basic Authentication in the token request. Here, Base64 Encoded Value of <client-id>:<client-secret> is used forAuthorization header in a token request. Once the pair is sent via token request, the Authorization server checks whether the pair is valid.
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
  • The client prepares some Data in JSON format including the Required claims defined in https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication and some other optional claims.
  • Generates a signature for the data using a client secret.
  • Include the data and the signature (JWT defined in RFC 7523)in the token request.
  • Since the authorization server already has the client secret, it can verify the signature.
  • Prepare a pair of private key and public key on the client side.
  • Make the public key accessible from the authorization server (eg: by jwks client metadata)
  • The client prepares some Data in JSON format including the Required claims defined in https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication and some other optional claims.
  • Generates a signature for the data using the private key.
  • Include the data and the signature (JWT defined in RFC 7523)in the token request.
  • The authorization server verifies the JWT using the public key.

private_key_jwt

01: Setup Authorization Server.

  1. In this guide, we use wso2is-5.10.0 as the authorization server. Download the zip file here and follow the product installation guide.
  2. Download private key JWT authenticator connector from IS connector store (find the compatible version) and copy it into<IS_HOME>/repository/component/dropins the directory. In case you couldn’t find a compatible version from the connector store you can try out building source code.
  3. To register the JWT grant type, configure the <IS_HOME>/repository/conf/deployment.toml file by adding a new entry as seen below.
[[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"

02: Prepare private key and public key in client side.

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

03. Make the public key accessible from the authorization server.

-----BEGIN CERTIFICATE-----
MIIDizCCAnOgAwIBAgIEC5FyUDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJT
TDEQMA4GA1UECBMHV2VzdGVybjEQMA4GA1UEBxMHQ29sb21ibzENMAsGA1UEChME
V1NPMjEUMBIGA1UECxMLRW5naW5lZXJpbmcxHjAcBgNVBAMTFUFudXJhZGhhIEth
cnVuYXJhdGhuYTAeFw0yMjAxMzAxMjA2NDlaFw0yMjA0MzAxMjA2NDlaMHYxCzAJ
BgNVBAYTAlNMMRAwDgYDVQQIEwdXZXN0ZXJuMRAwDgYDVQQHEwdDb2xvbWJvMQ0w
CwYDVQQKEwRXU08yMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEeMBwGA1UEAxMVQW51
cmFkaGEgS2FydW5hcmF0aG5hMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAgBTp05Ehn1jftFPl/pHW60wzhpRpAu8ogqisXJrgqzWqnkEHtc6Essrd1hXz
UOBijRreERgqEDDhg+ivr1KhCGJZqtGIeNzFntsRkRYUs9O4JmynBiSe/sj8+auN
ErGNyFxP4706lZx71CTc4/RIrc+HgY2wJEx4JeYidhIXJ0Yg7B0HxVL5NHEYQuDs
flZUkbXLqTYsHf1d+Tug6cuTlZhexcAZGWy7ICDAsfXESll5QpiqmMoiM/IDeBpI
J0+e1tr5s1rNoIRlWkCmCNuD//CzDTaQ8mStMQJ6duNSRngPwY1O/v4jbidhRLKS
vyO9mPpkGTDVJtLi4PZ8K6YBWwIDAQABoyEwHzAdBgNVHQ4EFgQUZbqdES651kfL
Ab4MJ/BYU/DFW2swDQYJKoZIhvcNAQELBQADggEBAETLbNPOHXgJEWExVKaTS5/o
riXf9HXVCoCjDJIEmbjfxQnqvQLXQWa3v6ElAGBCubzJ63zdU859nrNDhjrW3Gs5
qPOyq3WmXjPzAGMVl+fD3SW9l8t/bEVBxTDo7gRqEml554+OMtKraKgomPtwSd2b
9fccoosZBTkHba5T4zJ9pRrNBbpm8cdX7ukIBOCA8QHm2As9UTLexN9GjmUYMC9X
OLdcPj/sBR0FxuEdWHu72Gfn0pPsUKV0Ip5151BSAsEuTSf1TojaR0hGGF8/F3jR
4WwzA0c9LX8gtBpsL8objToTNOdpRclmN6k5dfu3iNmUvoewltbpGHy8RI+JuwU=
-----END CERTIFICATE-----

04: Prepare JSON data which conforms to the specification

{
"alg": "RS256",
"typ": "JWT"
}
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. 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 ID Token MUST NOT be accepted for processing.iat : OPTIONAL. Time at which the JWT was issued.
{
"iss": "RN0I55bldQftY97uNq9iIXQA21wa",
"sub": "RN0I55bldQftY97uNq9iIXQA21wa",
"exp": 1643650350,
"iat": 1643650346,
"jti": "10003",
"aud": "https://localhost:9443/oauth2/token"
}

05: Sign the data with the private key.

06: Send the token request to authorization server.

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=<call_back_url>" https://localhost:9443/oauth2/token
https://localhost:9443/oauth2/authorize?response_type=code&client_id=<cliend_id>&redirect_uri=<call_back_url>&scope=openid
curl -v POST -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=authorization_code&code=<authz_code>&scope=openid&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=<jwt_assertion>&redirect_uri=<call_back_url>" https://localhost:9443/oauth2/token

07: Authorization server extracts the client assertion from the token request, and verify by public key.

{"access_token":"ccbaa39f-7d4f-3efa-aa9a-ecfac5a8be3c","token_type":"Bearer","expires_in":3542}
[oauth.grant_type.client_credentials]
allow_id_token = true
{"access_token":"4c6bc82e-76ea-3e75-aa7e-7c3b8a4f15da","refresh_token":"77f1d999-d2f3-369d-89cd-16593de821e9","scope":"openid","id_token":"eyJ4NXQiOiJNell4TW1Ga09HWXdNV0kwWldObU5EY3hOR1l3WW1NNFpUQTNNV0kyTkRBelpHUXpOR00wWkdSbE5qSmtPREZrWkRSaU9URmtNV0ZoTXpVMlpHVmxOZyIsImtpZCI6Ik16WXhNbUZrT0dZd01XSTBaV05tTkRjeE5HWXdZbU00WlRBM01XSTJOREF6WkdRek5HTTBaR1JsTmpKa09ERmtaRFJpT1RGa01XRmhNelUyWkdWbE5nX1JTMjU2IiwiYWxnIjoiUlMyNTYifQ.eyJhdF9oYXNoIjoiTHFHT28yWGdtY2ZuVHlqR3FGd3JLUSIsImF1ZCI6IlJOMEk1NWJsZFFmdFk5N3VOcTlpSVhRQTIxd2EiLCJjX2hhc2giOiJ2RFFYRlVBd19sMXhkMUhGbGM4T2tBIiwic3ViIjoiYWRtaW4iLCJuYmYiOjE2NDM1NTcyNjIsImF6cCI6IlJOMEk1NWJsZFFmdFk5N3VOcTlpSVhRQTIxd2EiLCJhbXIiOlsiQmFzaWNBdXRoZW50aWNhdG9yIl0sImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTY0MzU2MDg2MiwiaWF0IjoxNjQzNTU3MjYyLCJzaWQiOiI4YWFjZjlhYy03MTI2LTQxZGUtYjRlOS1iOWVjMTYyYWZhYzIifQ.IkBeA9HRBMVxLNat-QwPp0mUQyxCNUorh63ML4oKNoEe_CWCOFYK5jmzj4NnnmKMm0OffELwTsQNyORyWY-aEmIhvy12OBRiP2ovtU9Pww7pJ7ogFb3WIkV-5N0h41yQ5mIbpS9AFgIr4F4wwaiWh6W8KfOOurB4Q0hr9zlSRovsydd40o0z5dU4zjocVPIKr4dalzlgir3dDoBgzs8wy0yAMLQC1ePDjHZvIPkeR7uHAK1badAnkshLIp-qjvXP96ar2jHVnOjcLsXwnssqLFsBSFSj-eVNmH2B5EobblATg_RweitRlaRTMRc7O5n6y5rf9x2lluYGIjIvx-HmkQ","token_type":"Bearer","expires_in":3600}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store