User consent gathering with Curity
The process where a user is asked to confirm the release of the scopes and claims that a client requests, is called user consent gathering. Once agreed, the scopes and claims can be bound to the delegation when issuing token. In case the user decides that the requested scopes and claims are invalid, you can enable the option to let a user select which requested scopes and claims are allowed before giving its permission to proceed with issuing the token.
In this tutorial, you will be guided through the process of setting up Curity for user consent gathering. This includes creating new and required scopes and make requests like a client would to see the user experience. Finally we'll look at customizing the template that renders the form that the user interacts with, as well as internationalizing the scope and claim names that are presented to the user.
We'll be using the Admin UI to make small configuration updates, but will also use the CLI so you can copy/paste configuration commands from the tutorial directly.
Token Service profile configuration
In order to follow this tutorial, you'll need a deployed version of Curity 3.0 or higher that exposes a Token Service profile with the following properties:
- We'll be using the Code Flow and the Implicit Flow to make requests, so enable these as Client Capabilities.
- As we'll be requesting profile claims, the Token Service profile must be enabled as OpenId Connect profile
oauth profile from the example configuration suffices
Authentication Service profile configuration
As the Token Service profile needs user authentication, it is configured with an Authentication Service.
oauth profile points to the
authentication profile from the example configuration, which will take care of our user
Requests are made using a client that we're adding with the following properties:
- The client_id is
uc-clientand client_secret is
- The client has code flow and implicit flow capabilities enabled
Client configuration in XML format (notice the profile id)
<config xmlns="http://tail-f.com/ns/config/1.0"> <profiles xmlns="https://curity.se/ns/conf/base"> <profile> <id>oauth</id> <type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type> <settings> <authorization-server xmlns="https://curity.se/ns/conf/profile/oauth"> <client-store> <config-backed> <client> <id>uc-client</id> <client-name>User Consent Client</client-name> <description>Client that is used to demonstratie User Consent</description> <secret>$5$8wnbFS1VBVzEJj9S$8Kni99O1JWOeLdwfrr6lYn6/8aryU4WMRWgQBRGz7Y.</secret> <redirect-uris>https://uc-client.example.com/callback</redirect-uris> <scope>admin</scope> <scope>openid</scope> <user-authentication> </user-authentication> <capabilities> <code/> <implicit/> </capabilities> <privacy-policy-url>https://uc-client.example.com/privacy-policy</privacy-policy-url> <terms-of-service-url>https://uc-client.example.com/terms-of-service</terms-of-service-url> </client> </config-backed> </client-store> </authorization-server> </settings> </profile> </profiles> </config>
You can upload this XML-snippet by storing its contents in a file, let's say
uc-client.xml, store the file in
etc/init, and then run
bin/idsvr -r to load the file into the running server's configuration.
Enable user consent for a client
Now that we have a configured profile and a client that can request
Use the Admin UI to toggle the User Consent switch to the enabled state:
Don't forget to commit the change.
Alternatively, you can use the following command from the terminal to do the same thing (notice the profile id and the client id, and adjust as needed when re-using this command) :
bin/idsh -s <<< "configure set profiles profile oauth oauth-service settings authorization-server client-store config-backed client uc-client user-consent commit "
Now, with the Token Service profile and client configured with user consent enabled, you can make a request using the Implicit Flow to ask for an Access Token. Assume the client redirects you to get a token, then the URL where you would be redirected to, would be
You will be taken to the Authentication Service to authenticate as a user. You can use the credentials as provided in the test configuration, or take a look at the paragraph How to add a test user in the Curity documentation.
After authenticating, the following screen will be shown, where the user is asked to provide its consent to authorizing the client to get a token on its behalf:
Go ahead, allow the token to be issued, and you will be redirected back to the client's redirect uri with a token in the fragment.
Re-use of active user consent
Now that a delegation that is bound to the user as well as to the consented-to scope
openid is issued, it will be
known to Curity that the user has consented to releasing access to this scope. Given the previous step has resulted
in the issuance of a token after you consented to the request, try to make a new request for a token by the client for
the same scope. You can use the exact same url to initiate the second request (remember it's good practice to make the
state unique for each request)
You can see now that both SSO as well as re-use of user consent kicks in, and the response to this request will be a newly issued token.
Make a client request with prompt=consent, prompt=allow_scope_deselection
In case you want the user to consent to requested claims, regardless of pre-existing delegations, you can make that
same request, but include the parameter
prompt=consent in the querystring, like:
Notice how, after authenticating, possibly through SSO, the consent screen is being shown as a result of this request.
Try and hit
Cancel. The response will now be a redirect back to the client that includes an error in the fragment.
This will inform the client with the reason why the response did not contain a token, as was requested.
Allow consent deselection
When a request to authorize a token is being made, the client typically includes scopes, to represent the type of
access it desires. It is good practice to ask for the minimal set of scopes, in which case the client needs all scopes
to be able to perform the tasks it intends to perform using the token. Some scopes or claims can be optional however.
An example would be the
firstname claim - a client would like to know this, but if it does not have access to it, it
might still be able to carry on with its work. In this case, the user may be given the choice to deselect access to
particular requested claims.
By default, the user is not allowed to deselect any requested scopes through the user consent form, but the client can be configured to always allow requested scope deselection. Alternatively, the client can indicate on a per-request basis whether scope deselection is allowed.
Consent deselection in configuration
When configuring the client, as we did in step 3 of this tutorial,
User Consent can be enabled. When this is enabled,
it also shows the option
Allow Deselection. When this is enabled, all requested claims are deselectable (unless they
are marked as being required, more on this later in this tuturial).
Allow Deselection option for the client
uc-client in the configuration, and commit the change.
Now, make a new request, including the
prompt=consent parameter in the querystring:
Notice how the checkbox before the
admin-claim becomes deselectable.
If you deselect the
admin-scope and press the
Submit Consent button, the issued token will only contain the
openid scope. This is also reported in the
scope response parameter in the fragment of the return URL to where the
user is redirected.
Consent deselection per request
The next step is to go back to the client configuration, and revert the
Allow Deselection setting, so that it is now
disabled. Commit the change.
Now we'll make a request that indicates that the claims are deselectable for that request. We do this by including
consent_allow_deselection in the
prompt parameter value:
The user consent form is shown again, where claims are deselectable again, just like when the configuration was set to allow deselection.
Scopes and consent
So far, we've been making requests using the scopes
admin, both of which are configured as non-required
scopes. In case a client should always be issued the
admin scope, you would set it to be required.
To change this, open the Admin UI and go to the Scopes page in the Token Service profile's configuration. Enable the
required slider of the
admin scope, and commit the changes.
Ensure that no delegations are active for the test user (i.e. deactivate or delete existing delegations from the database), and make a new request for a token:
Adding "special" scopes (OpenId Connect)
When a scope is requested, Curity will consider special scopes. A special scope is defined in OpenId Connect to
represent a group of claims. Special scopes are
address. Let's add the
You can add this scope through the Admin UI, but you can also issue this command from the command line:
bin/idsh -s <<< "configure set profiles profile oauth oauth-service settings authorization-server scopes scope email description OpenId\ Connect\ email\ scope set profiles profile oauth oauth-service settings authorization-server scopes config-backed client uc-client scope email commit "
Remember to also add the scope to the list of allowed scopes of client
uc-client, and to commit the changes.
Now, see what happens when you request the
You should see the consent form asking for consent for
Verified Email and
User ID, while the request asked
email_verified, according to the OpenId Connect specification.
Note: if you didn't remove the
Required setting from the
admin-scope (from the previous step), the request will
result in a redirect with an
invalid_scope error, pointing out that a required scope was not requested. Remove the
Required setting from the
admin scope by issuing the following shell command:
bin/idsh -s <<< "configure delete profiles profile oauth oauth-service settings authorization-server scopes scope admin required commit "
When user consent is being asked, the requested claims can be localized when they are rendered. Let's translate the
sub claim so it can be rendered in dutch.
The translations must be made in the language override file for the OAuth consent template, which is located at
usr/share/messages/overrides/is the base directory for language overrides,
nl/is the language prefix, and
views/oauth/is the path to the OAuth templates
Added up, the override file for dutch must be stored as
In this file, you can translate the claim names, as well as the claim descriptions, according to the following format:
consent.claim-names.<claim-name>=Some name consent.claim-descriptions.<claim-name>=Description of the claim
To translate the claim
admin, let's create that
messages-file with the following contents:
consent.claim-names.sub=Gebruikersnaam consent.claim-descriptions.sub=De naam waarmee je inlogt
Now to see the translated consent screen in action, force the locality of the request to be dutch by using the
ui_locales parameter in the querystring:
Notice the translations of the claims follow the translations as they are made in the overridden
Customizing the user consent template
The consent screen is rendered from a Velocity template, which you can change. Note that there are a couple of items in the template that are rendered from configuration, which are:
- a client logo
- the terms of service URL
To modify the consent template itself, you can override it by creating a new file called
consent.vm in the directory
usr/share/templates/overrides/views/oauth. For the sake of this tutorial, let's switch the order of the Privacy
Policy URL and the Terms of Service URL.
Start out with a copy of the provided consent template. First ensure that the directory exists, then copy it:
mkdir -p usr/share/templates/overrides/views/oauth cp usr/share/templates/core/views/oauth/consent.vm usr/share/templates/overrides/views/oauth
Next, use your editor to open the
consent.vm from the
overrides/ directory, and move around the order of the two
URLs, to make it look like this:
#if ($_client.get("termsOfServiceUrl")) <a href="$!_client.get("termsOfServiceUrl")">#message("views.oauth.consent.termsOfService")</a> #end #if ($_client.get("policyUrl")) <a href="$!_client.get("policyUrl")">#message("views.oauth.consent.policyDocument")</a> #end
Save the file, and make a request for a token, asking for consent:
That's how easy it is to change a template!
Deleting delegations from the database
To perform simple database management, you can use the
hsqltool that is included in the
bin/ directory of the
installation directory. In case the server is running, connect to the database as a server (instead of pointing to the
files and requiring a lock on them). Delete all issued tokens and delegations by issuing the following command from
bin/hsqltool --inlineRc=url=jdbc:hsqldb:hsql://localhost:9001/db,user=sa,password= --sql "DELETE FROM \"tokens\"; DELETE FROM \"delegations\";"