JSON Web Token (JWT) Parser
Problem this snippet solves:
This feature is now native in v13.1 and it is strongly recommended you implement the native solution instead. This code is left only as an example for future use cases, it should not be used for JWT handling because there is no signature validation.
This code parses a JWT (JSON Web Token) received by a Big-IP acting as an OAuth client and creates session variables for the JSON parameters in the header and payload. Example use cases might include Azure AD B2C or Azure AD Enterprise integration.
This iRule does not perform signature validation.
Code from the "Parse and Set Session Variables" section down could be easily harvested for other JSON parsing use cases that do not need the JWT decoding.
How to use this snippet:
Attach this iRule to the virtual server receiving the JWT that is configured for OAuth. Inside the VPE after the OAuth Client agent add an iRule agent with id jwt-parse.
This iRule will set several variables including: session.oauth.jwt.last.header session.oauth.jwt.last.payload session.oauth.jwt.last.signature
In addition it will create a session variable for each parameter in the header and payload in the following syntax. session.oauth.jwt.header.last.* session.oauth.jwt.payload.last.*
You can then call these session variables elsewhere.
Code :
when ACCESS_POLICY_AGENT_EVENT { if { [ACCESS::policy agent_id] eq "jwt-parse" } { #log local0. "JWT-Parse: Started" #Get the JWT set jwt [ACCESS::session data get -secure session.oauth.client.last.id_token] #log local0. "JWT-Parse: JWT Received - jwt is $jwt" #Separate the header, payload, and signature set jwt_header [getfield $jwt "." 1] ACCESS::session data set session.oauth.jwt.last.header $jwt_header #log local0. "JWT-Parse: Header extracted - jwt_header and session.oauth.jwt.last.header are $jwt_header" set jwt_payload [getfield $jwt "." 2] ACCESS::session data set session.oauth.jwt.last.payload $jwt_payload #log local0. "JWT-Parse: Payload extracted - jwt_payload and session.oauth.jwt.last.payload are $jwt_payload" set jwt_signature [getfield $jwt "." 3] ACCESS::session data set session.oauth.jwt.last.signature $jwt_signature #log local0. "JWT-Parse: Signature extracted - jwt_signature and session.oauth.jwt.last.signature are $jwt_signature" #Base 64 decode the header and payload #Fix encoding issues in header set jwt_header_modified $jwt_header set tail [string length $jwt_header_modified] if {$tail % 4 == 2} { append jwt_header_modified {==} } elseif {$tail % 4 == 3} { append jwt_header_modified {=} } #log local0. "JWT-Parse: Header encoding fixes complete - jwt_header_modified is $jwt_header_modified" #Fix encoding issues in payload set jwt_payload_modified $jwt_payload set tail [string length $jwt_payload_modified] if {$tail % 4 == 2} { append jwt_payload_modified {==} } elseif {$tail % 4 == 3} { append jwt_payload_modified {=} } #log local0. "JWT-Parse: Payload encoding fixes complete - jwt_payload_modified is $jwt_payload_modified" #Base64 decode set jwt_header_modified [b64decode $jwt_header_modified] #log local0. "JWT-Parse: Header Base 64 decoded - jwt_header_modified is $jwt_header_modified" set jwt_payload_modified [b64decode $jwt_payload_modified] #log local0. "JWT-Parse: Payload Base 64 decoded - jwt_payload_modified is $jwt_payload_modified" #Parse and Set Session Variables #Remove JSON characters set jwt_header_modified [string map {\{ {} \} {} \[ {} \] {} \" {}} $jwt_header_modified] #log local0. "JWT-Parse: Header JSON Characters removed - jwt_header_modified is $jwt_header_modified" set jwt_payload_modified [string map {\{ {} \} {} \[ {} \] {} \" {}} $jwt_payload_modified] #log local0. "JWT-Parse: Payload JSON Characters removed - jwt_payload_modified is $jwt_payload_modified" #Split into field/value pairs set jwt_header_modified [split $jwt_header_modified ,] #log local0. "JWT-Parse: Header Fields split - jwt_header_modified is $jwt_header_modified" set jwt_payload_modified [split $jwt_payload_modified ,] #log local0. "JWT-Parse: Payload Fields split - jwt_payload_modified is $jwt_payload_modified" #Set APM session variables for each header parameter foreach parameter $jwt_header_modified { set variable_name [getfield $parameter ":" 1] set variable_value [getfield $parameter ":" 2] ACCESS::session data set session.oauth.jwt.header.last.$variable_name $variable_value #log local0. "JWT-Parse: Header session variable set - session.oauth.jwt.header.last.$variable_name is $variable_value" } #Set APM session variables for each payload parameter foreach parameter $jwt_payload_modified { set variable_name [getfield $parameter ":" 1] set variable_value [getfield $parameter ":" 2] ACCESS::session data set session.oauth.jwt.payload.last.$variable_name $variable_value #log local0. "JWT-Parse: Payload session variable set - session.oauth.jwt.payload.last.$variable_name is $variable_value" } } }
Tested this on version:
13.0- MarvinCirrocumulus
Hi graham I got the Irule working as a workaround, I did need to trim the payload variable name because it had spaces in the name preventing it from being created like this.
session.oauth.jwt.payload.last. value is dehaasm
So I added one line to trim the variable name and now it works. I would like to prefer if this information is saved inside the F5 code by default as you stated. Thanks for sharing the Irule.
#Set APM session variables for each payload parameter foreach parameter $jwt_payload_modified { set variable_name [getfield $parameter ":" 1] set variable_name_trim [string trim $variable_name] set variable_value [getfield $parameter ":" 2]
- MarvinCirrocumulus
Hi Graham, I am using F5 version 13.1.3 and I have a F5 SAML IDP which performs oauth client to another F5 config which functions as the oauth server, I receive the JWT access token properly however the user information is not saved in variables I only have the JSON encoded access token. You explain that this would be available inside version 13.1?
- Walter_KacynskiCirrostratus
Are these JWTs signed?
- Reshma_129342Nimbostratus
Need another help, can you let meknow when we will get below error and how to resolve - extra switch pattern with no body while executing "switch -regex $http_uri { "/web-service/rest/logger/console" - "/web-service/rest/security/securityquestionsauth" - "/web-service/rest/secu..." ("/web-service/" arm line 16) invoked from within "switch -regex $http_uri { "/idcapture." - "/favicon." { log local0.info "10.0206 - Unprotected resource requested for idcapture URI: [HTT..."
- Reshma_129342Nimbostratus
Thanks GRaham. My intension is to write a basic iRule which will validate allow user to web service if comoddo certificate is available(Installed on browser) or allow request to web service if Authorization http header containing JWT token is available. I need to basically validate age of the token from exp param
Sample decrypted JWT Token: {"sub":123456789,"tkid":12345,"term":123456789,"exp":1548159789,"int":10}
Can you suggest basic iRUle to validate Authorization for exp time.
- Graham_Alderso1Employee
Reshma,
It is because the iRule has ACCESS commands in it that cannot function without an access profile assigned. The ACCESS commands are there to assign the JWT values into APM session variables.
To resolve this problem you must follow the directions provided by the error message and assign an access profile (enabled by licensing and provisioning APM) onto the virtual server before you can assign this iRule.
It is possible to comment out any ACCESS commands from this iRule and use in LTM only as an alternative. However, as noted at the top and by Walter, this does not verify the signature of the JWT and may have performance problems at large scale. The built-in JWT parsing functionality in APM is highly recommended to be used instead and is the supported method of doing this.
- Walter_KacynskiCirrostratus
Please heed the warning at the top: This feature is now native in v13.1 and it is strongly recommended you implement the native solution instead. This code is left only as an example for future use cases, it should not be used for JWT handling because there is no signature validation.
What are you trying to accomplish with this functionality and what version are you on?
- Reshma_129342Nimbostratus
Hi , I am trying to copy this above code in my iRUle in LTM , but I am getting below error. Can you guide me to use JWT parsing with my LTM iRule
01071912:3: ACCESS_POLICY_AGENT_EVENT event in rule (/Common/Tablet4) requires an associated ACCESS profile on the virtual-server (/Common/tabletfour)
my current iRule code is somehting below, please let me know how can I integrate in current iRule in LTM.
when CLIENTSSL_CLIENTCERT { HTTP::release if { [SSL::cert count] < 1 } { reject } }
when HTTP_REQUEST { if { [SSL::cert count] <= 0 } { HTTP::collect SSL::authenticate always SSL::authenticate depth 9 SSL::cert mode require SSL::renegotiate
} }
when HTTP_REQUEST_SEND { clientside { if { [SSL::cert count] > 0 } {
HTTP::header insert "ClientCert-Subject" [X509::subject [SSL::cert 0]] }
} }
- Graham_Alderso1Employee
Sure. The current iRule is written in an APM event and sets APM session variables, but you could change those to standard variables and an LTM event (like when HTTP_REQUEST and then restrict run by URI or something).
Is this id_token possible to extract in LTM module outside APM?