OAuth 2.0 Dynamic Client Registration
Problem this snippet solves:
OAuth 2.0 is now supported in version 13. Client applications need to be defined manually in the Web UI. We developed an irule allowing a client application to self register.
How to use this snippet:
This code makes calls to external functions : json2dict and HTTP Super SIDEBAND Requestor. You need to add them to your set of irules on the BIG-IP before configuring the client app registration service.
json2dict
proc json2dict JSONtext { string range [ string trim [ string trimleft [ string map {\t {} \n {} \r {} , { } : { } \[ \{ \] \}} $JSONtext ] {\uFEFF} ] ] 1 end-1 }
Workflow
The client application will do the following request :
POST /f5-oauth2/v1/client-register HTTP/1.1 Host: oauthas.example.com User-Agent: curl/7.47.1 Accept: */* Content-Length: 29 Content-Type: application/x-www-form-urlencoded username=user&password=pwd
This request can be achieved using cURL :
curl -k -vvv https://oauthas.example.com/f5-oauth2/v1/client-register -d 'username=user&password=pwd'
Quick notes
The irule is configured to create Client Applications with Resource Owner Password Credentials Grant (ROPC) mode activated. The irule needs to be modified to activate other modes
Update (2018-05-01)
- Add a way to configure static client_id and client_secret
Update (2018-01-12)
- URI decode username and password parameters
Update (2017-11-07)
- Use HTTP::collect to workaround issues with large body
Update (2017-10-25)
- Enhance JSON result parsing and attribute retrieval
Code :
when RULE_INIT { ### # credentials required to access iControl REST API ### set static::adm_user "admin" set static::adm_pwd "admin" ### # settings required to authenticate the user trying to register an application ### set static::timeout 300 set static::lifetime 300 set static::access_profile "/Common/ap-ldap-auth" ### # settings required to update the APM configuration with the newly created ClientApp configuraiton ### set static::adm_partition "Common" set static::oauth_profile "my-oauth-profile" set static::scopes "myscope" ### # settings required to sync the OAuth 2.0 Authorization Server access profile ### set static::oauth_access_policy "ap-oauth-auth-server" ### # settings required to publish the client registration service ### set static::client_register_uri "/f5-oauth2/v1/client-register" set static::host "oauthas.example.com" ### # Define a static client_id and client_secret ### set static::use_static_app 1 set static::client_id "xxxxxxxxxxxxxxxxxxxxxxx" set static::client_secret "xxxxxxxxxxxxxxxxxxx" } when CLIENT_ACCEPTED { ### # When we accept a connection, create an Access session and save the session ID. ### set flow_sid [ACCESS::session create -timeout $static::timeout -lifetime $static::lifetime] } when HTTP_REQUEST { ### # initialize vars ### set username "" set password "" set name "" set client_app "" set scopes "" set client_id "" set client_secret "" set agent "" set timestamp [clock seconds] switch -glob [string tolower [HTTP::header "User-Agent"]] { "*android*" { set agent "android" } "*ios*" { set agent "ios" } default { set agent "default" } } ### # identify client registration request. The client applicaiton needs to do a POST request on client registration URI and provides username and password ### if { [HTTP::path] eq $static::client_register_uri and [HTTP::host] eq $static::host and [HTTP::method] eq "POST" } { HTTP::collect [HTTP::header Content-Length] } } when HTTP_REQUEST_DATA { set username [URI::decode [URI::query "/?$payload" username]] set password [URI::decode [URI::query "/?$payload" password]] ### # play inline ACCESS policy to validate user credentials ### ACCESS::policy evaluate -sid $flow_sid -profile $static::access_profile session.logon.last.username $username session.logon.last.password $password session.server.landinguri [string tolower [HTTP::path]] if { [ACCESS::policy result -sid $flow_sid] eq "deny" or [ACCESS::policy result -sid $flow_sid] eq "not_started" } { HTTP::respond 403 content "{\"error\": \"Invalid user credentials\",\"error-message\": \"Access denied by Acces policy\"}" noserver Content-Type "application/json" Connection Close ACCESS::session remove -sid $flow_sid event disable all } ACCESS::session remove -sid $flow_sid if { !$static::use_static_app } { ### # generate client name and client application name ### set username [string map -nocase { "@" "." } $username] set name "$username-$agent-$timestamp" set client_app $name set scopes $static::scopes ### # prepare and execute API REST call to create a new client application. Endpoint: /mgmt/tm/apm/oauth/oauth-client-app ### set json_body "{\"name\": \"$name\",\"appName\": \"$client_app\",\"authType\": \"secret\",\"grantPassword\": \"enabled\",\"scopes\": \"$scopes\"}" set status [call /Common/HSSR::http_req -state hstate -uri "http://127.0.0.1:8100/mgmt/tm/apm/oauth/oauth-client-app" -method POST -body $json_body -type "application/json; charset=utf-8" -rbody rbody -userid $static::adm_user -passwd $static::adm_pwd ] set json_result [call /Common/sys-exec::json2dict $rbody] if { $status contains "200" } { ### # extract client_id and client_secret from JSON body ### set client_id [lindex $json_result [expr {[lsearch $json_result "clientId"]+1}]] set client_secret [lindex $json_result [expr {[lsearch $json_result "clientSecret"]+1}]] ### # prepare and execute API REST call to bind the client application to the OAuth profile. Endpoint: /mgmt/tm/apm/profile/oauth/~$static::adm_parition~$static::oauth_profile/client-apps ### set json_body "{\"name\": \"$name\"}" set status [call /Common/HSSR::http_req -state hstate -uri "http://127.0.0.1:8100/mgmt/tm/apm/profile/oauth/~$static::adm_partition~$static::oauth_profile/client-apps" -method POST -body $json_body -type "application/json; charset=utf-8" -rbody rbody -userid $static::adm_user -passwd $static::adm_pwd ] set json_result [call /Common/sys-exec::json2dict $rbody] ### # if binding is successful, respond to the client with client_id and client_secret ### if { $status contains "200" } { ### # Prepare and execute API REST call to apply Access Profile after Client Application has been assigned to OAuth profile ### set json_body "{\"generationAction\": \"increment\"}" set status [call /Common/HSSR::http_req -state hstate -uri "http://127.0.0.1:8100/mgmt/tm/apm/profile/access/~$static::adm_partition~$static::oauth_access_policy" -method PATCH -body $json_body -type "application/json; charset=utf-8" -rbody rbody -userid $static::adm_user -passwd $static::adm_pwd ] set json_result [call /Common/sys-exec::json2dict $rbody] if { $status contains "200" } { HTTP::respond 200 content "{\"client_id\": \"$client_id\",\"client_secret\": \"$client_secret\"}" noserver Content-Type "application/json" Connection Close event disable all } else { HTTP::respond 403 content "{\"error\": \"Synchronization failed\",\"error-message\": \"[lindex $json_result 3]\"}" noserver Content-Type "application/json" Connection Close event disable all } } else { HTTP::respond 403 content "{\"error\": \"ClientApp binding failed\",\"error-message\": \"[lindex $json_result 3]\"}" noserver Content-Type "application/json" Connection Close event disable all } } else { HTTP::respond 403 content "{\"error\": \"ClientApp creation failed\",\"error-message\": \"[lindex $json_result 3]\"}" noserver Content-Type "application/json" Connection Close event disable all } } else { HTTP::respond 200 content "{\"client_id\": \"$static::client_id\",\"client_secret\": \"$static::client_secret\"}" noserver Content-Type "application/json" Connection Close event disable all } }
Tested this on version:
13.0- Herve_BossantNimbostratus
Thanks for tyour contribution and your support. Nice piece of code.
- Or_YaacovRet. Employee
Nice Code Yann
In addition, 13.1.0.8 will introduce templates registration for client applications through the guided configuration framework. Your automation code is superb. Thanks for posting
- Walter_KacynskiCirrostratus
I can find no code for the guided configuration. I have opened a support case to inquire on it's location.
- Walter_KacynskiCirrostratus
Support says that Guided configuration is not yet available.
- Or_YaacovRet. Employee
@Walter Wacynski , Note AGC are available from 13.1.0.8 while re-licensing your APM
- Walter_KacynskiCirrostratus
Thanks, I have it available now... but not on LAB licenses :(
- ShawnRNimbostratus
Are there any plans to support OID DCR from the official spec?
https://openid.net/specs/openid-connect-registration-1_0.html
- EelkeAltocumulus
looks nice, will try to see if it works on v16