Encryption/signing of the request object
As per OIDC specification, you can optionally use the request
parameter to enable signed and optionally encrypted requests. This usually isn't required but can be used as an additional security measure. It can also be used to comply with any possible Full Message-Level Encryption (MLE) requirements. A common use case is to mask any personal information contained in the request and also to send requests that cannot be tampered with by any third parties.
Additional hardening of request object mechanism
There are two things you can do to additionally harden the security:
Requires Request Object
- In the Signicat Dashboard, navigate to Products > eID Hub > OIDC clients.
- Select the client you wish to use. If you haven't created a client yet, see Set up an OIDC client.
- In the client menu, navigate to Advanced > Security and tick Requires Request Object.
This configuration rejects any request that does not use a request object for this client. Effectively, it forces all requests on this client to be at least signed.
Unique/One-Time-Use Request Object
If you submit a request object token that contains the jti
claim, we will ensure that it is unique for this client within 24 hours. This will work automatically without additional configuration required.
If you do not wish to have unique request objects enforced, simply omit the jti
claim.
This mechanism can effectively prevent “replay attacks” when using request objects, as each request object token can only be used once.
Type of request objects
The Signicat OIDC solution accepts two types of request objects:
- Signed JWT: A valid JWS token.
- Nested JWT: A token that is first signed (JWS) and then encrypted (JWE).
This guide focuses on nested JWTs.
Prerequisites
Before configuring an application that authenticates using nested JWTs, you must generate an RSA key pair. You can either:
- Import the public part of an existing key pair into the Signicat Dashboard, or
- Generate an RSA key pair directly in the Signicat Dashboard.
Import the public key
If you wish to generate your own key pair, make sure you meet these technical requirements:
- Generate a JWK RSA key pair (2048-bit) suitable for encryption. We also support 4096-bit if required. It is important that you create a JWK or an X509 certificate. We recommend using the JWK format.
- Store the public part of your key pair in a text file (or
.json
).
If you chose to create your own key pair, then in the next section click Import public key instead of Add public key in step 2.
Generate the key pair
To generate a key pair in the Signicat Dashboard:
- In the Signicat Dashboard, navigate to Products > eID Hub > OIDC clients.
- Select the client you wish to use. If you haven't created a client yet, see Set up an OIDC client.
- In the client menu, navigate to Advanced > Public keys and then Add public key.
- Fill in the Name, Not valid before and Not valid after fields for your key.
- Select Signing as the Usage.
- Now press Create. If you want to upload your public key, either paste your key content or upload a file with the key.
- A key pair is generated for you. This contains a public key and a private key. Take a copy of the private key and store it somewhere safe.
You are now ready to move on to the implementation.
Implementation
Begin crafting the OIDC authorisation request. The parameters are standard OIDC parameters. The requirements are as follows:
- The HTTP request can be made using either GET or POST.
- The payload must be a request object (per OIDC Core specifications, section 6.1). An example of a typical payload request object looks as follows:
{
"client_id": "dev-annoyed-sloth-492",
"response_type": "code",
"redirect_uri": "https://oauth.tools/callback/code",
"acr_values": "idp:ftn",
"state": "ABCDEF012345",
"scope": "openid profile",
"iss": "dev-annoyed-sloth-492",
"aud": "https://team-connect-demo.test.signicat.dev/auth/open",
"iat": 1673955575,
"exp": 1673961575,
}
As seen above, the JWT requires the following additional claims to be valid:
- Create a signed JWT using a payload similar to the one above. Serialise this as a compact format JWT. The serialised JWT is a long string.
- Now create an encrypted JWT using the serialised signed JWT from above as the payload. This should again be serialised as a compact format JWT.
- Use this nested JWT as your request object towards Signicat. For example,
/auth/open/connect/authorize?client_id={CLIENT_ID}&request={REQUEST_OBJECT_JWT}
.
Code example
This code example is for illustrative purposes only. It is not intended for production usage. Signicat takes no responsibility if this code example is used inappropriately.
import json
from time import time
from jwcrypto import jwk, jwe, jws
# For the sake of simplicity, the JWKs has been manually loaded into a dictionary.
# In a real life scenario you should do this in a secure manner: The jws_private_key should be stored securely on your premise.
# The jwe_public_key should be dynamically fetched from Signicat's JWKS endpoint.
jws_private_key = {
"alg": "RS256",
"d": "IfHsADmK2xsxbie2Q4pX5cOEfWEeD8efIC82_PtK7AT-ZO3vwll9WUQ_VMWXZuUkmDDwuLukp-GCAq871YfsJKTEXYibArYZSB6yPo7FHMxtEtsfOqDGEjCJHd76AE99ZH8qA2H_0j_xGMGbAP44x5DoTAidj1B3fKEF_IQfoHNfvQBwLaE2lyJVxQp_Xzay_VbdeGRlacmzc8ccGWU3lpAYDONMPxh0Z9tx2aexarDOjFbZ0jKN9b9-Hyntnz7iOFdiOWSHo41NOd5l2BG4SARdRPUwoMxcEexxVVPjpr1smNwLHJZdAvOMiHSAKDAwKh4Vgu4p5CfD6O6JM_PoSQ",
"dp": "TSi83Xlf9XP9DMeqCQgMXUcrxt4nSh56C0RnqRPneGShlObkUZmxJoaEh5TL5OJ-9MQKcGCBN0_LOFls1p4NOvdhZxxwHi2hEZisdd40IY37Z-4JN0L5Xs4jqLDrZAP_paEW1SOI33sqhHCeLi034DZw0-4BxKXm5Fhn-uS2WZ8",
"dq": "jSvo3MsPzigQN_20txhQFxqQaw6M7udXgt51QW50tnM896DlpEd0hbcryOncpfc8AVhjyxLnGG2sYQRLswcq838STu4wDEhuPfhgKAak_5cc38N_TSyRJWLfnwF9pTTWPgkk0Io8MwWpsIpf9qIbyBOl0jHnBdfSuuLKoYM6QfU",
"e": "AQAB",
"kid": "abc123321cba",
"kty": "RSA",
"n": "muNJBpq6VtZbGj0kb5YeMe5lE14CZ4OAKnY6Epzholp61rVrrWRlErmyV9C8J4G6jCgDKrGnjQp143gD71ATVLIus__wO1YtRplKCyz2hypvhvId8GwYOLm1k3TCjKeSa6DwKiKZZOUg011NWN9TSSkCdWb-xNgSV5gZesC_JngofTyrAXT92MDIzGoCMpA1D6tDIzadIigHA7_FznpT1eN5cAHZqMeRC8MHRH2_K8erUxx4QFuencrADmaf4vIWjTlmPioxa4XLRrYcXsnKrrKeVp8CXRFqMwb0fAtRmvQu3RHUqZ02dlTER7ocIbrHeYx2_VAxhqFtLhR9J2ih0w",
"p": "zpfR9mwYoPB-r1wTy5owD8RaoufPeVnabefe7IK9nwGUOkgINHDGTRPvXx2f0UuKz9kAVB6ZIsPgwCfhoMrbdQemNLXu5VZ_MRny6Uesk_5Awox5QIBbYvzF6PnMfV-pwLtieNLpiN_sP-pF6jfvJ8cy3XFBUnF5IDy0zeuI4Lc",
"q": "v-3u72-IPZpVuY8hkqi8gPpc5FTSWh-MJ5BvOcifDFjVYEE-KCTL5I2MJ2R9u-1sVceeFwfa-HfgV3ArizO91-pSJSqk-Py3KqHDXXdno30wwwJWR8noSwH8eRwqdQHBQQPa0ClywQJ2bbqiiw30CpjXDHknL5qXQEskpoJq88U",
"qi": "SGo4PyG344Uxddy_g_YGTt5pa_lvq0G42jGWU-fOPQfjSiNWhzuyflys9YoSf_x3kzwf1oJosdUdCpfUXzLX1LHsWrUld-52JFPtuFT3nNVZiFXCnboUHJSIlgaP1GlA1rHhh491gMIPqr4uJY3BSKp7aj2efL61Z-1l-LvwrSw",
"use": "sig"
}
jwe_public_key = {
"kty": "RSA",
"use": "enc",
"kid": "sandbox-encryption-key-0eb954d0e12b824b80a3c5664232320a",
"e": "AQAB",
"n": "trU6figprTeSBBTdW7Lqm6OjgNg4neNSvtEUs__bf5iK59W9oH2oeZLPx7ixmuJ3Cane6WHCR4BumxtnoIixWlNAJgdu_xosI-zO_7fhUVIgS-qH5kY9rj8GUa6GUquAV92_L3nnEWgKJ_220jV8_kdaqab1Pm5QMJ9RSC73BWuvCZ5fWb57okFN5-dTtjZ-WCmgij-9axPKjlM0PTp4c8Lm8KJDO-B-6n8DO9JEfdBpa6ejEIGi3tNJAyveluTzZ5YyF_WFD4rJZ2JIJIuGth7-o1Myq5QhmaXBTrL01aY-bLgiqWrtBNpM2rnU3MMUQn0TOpzKVvRaTl-x5HIpvw",
"alg": "RSA-OAEP"
}
# Loading keys
jwk_jws_private_key = jwk.JWK()
jwk_jws_private_key.import_key(**jws_private_key)
jwk_jwe_public_key = jwk.JWK()
jwk_jwe_public_key.import_key(**jwe_public_key)
# Token payload (JSON)
payload = {
"client_id": "dev-annoyed-sloth-492",
"response_type": "code",
"redirect_uri": "https://oauth.tools/callback/code",
"state": "ABCDEF012345",
"scope": "openid profile",
"iss": "dev-annoyed-sloth-492",
"aud": "https://team-connect-demo.test.signicat.dev/auth/open",
"iat": int(time()),
"exp": int(time()+6000),
}
payload_bytes = json.dumps(payload).encode('utf-8')
# Token headers
jws_header = {
"alg": jws_private_key["alg"],
"kid": jws_private_key["kid"],
}
jwe_header = {
"alg": jwe_public_key["alg"],
"enc": "A128CBC-HS256",
"kid": jwe_public_key["kid"],
}
# Creating JWS token
jwstoken = jws.JWS(payload_bytes)
jwstoken.add_signature(jwk_jws_private_key, protected=jws_header)
jws_serialized = jwstoken.serialize(compact=True)
# Encoding JWE token
jwetoken = jwe.JWE(jws_serialized, recipient=jwk_jwe_public_key, protected=jwe_header)
jwe_serialized = jwetoken.serialize(compact=True)
print(f"https://team-connect-demo.test.signicat.dev/auth/open/connect/authorize?client_id={payload['client_id']}&request={jwe_serialized}")
Detailed tutorial and code example
For an even more detailed description of how to set this up, including code examples, see Code examples > Signed and encrypted tokens (Advanced).