It is likely that the person who creates client definitions will not be the same person who set up the server. Moreover, it may be necessary to create client definitions on an ongoing basis. For this reason, the task of creating client definitions is included as a stand-alone section, at the end of the article.
Implementing the Custom Methods for the InterSystems IRIS Authorization Server
To customize the behavior of the authorization server, define classes as described in Code Customization Options. Then use this section for information on defining methods in those classes, depending on the processing steps that you want to customize.
-
Optional custom processing before authentication
-
Identifying the user
-
Validating the user and specifying claims
-
Optionally displaying permissions to the user
-
Optional custom processing after authentication
-
Generating the access token
After these subsections, a final subsection describes how to validate the client, in the case when this server must support the client credentials grant type. The client credentials grant type does not use steps 2 – 4 of the preceding list.
Optional Custom Processing Before Authentication
The information here applies to all grant types.
To perform custom processing before authenticating the user, implement the BeforeAuthenticate() method of the Authenticate class. This method has the following signature:
ClassMethod BeforeAuthenticate(scope As %ArrayOfDataTypes,
properties As %OAuth2.Server.Properties) As %Status
Where:
In your method, optionally modify either or both of these arguments, both of which are later passed to the methods used to identify the user. The method must return a %StatusOpens in a new tab.
Normally, there is no need to implement this method. However, one use case is to implement the launch and launch/patient scopes used by FHIR®, where the scope needs to be adjusted to include a specific patient.
Identifying the User
The information here applies only to the authorization code and implicit grant types.
To identify the user, implement the DisplayLogin() method of the Authenticate class. The DisplayLogin() method has the following signature:
ClassMethod DisplayLogin(authorizationCode As %String,
scope As %ArrayOfDataTypes,
properties As %OAuth2.Server.Properties,
loginCount As %Integer = 1) As %Status
Where:
-
authorizationCode
-
scope is an instance of %ArrayOfDataTypesOpens in a new tab that contains the scopes contained in the original client request, possibly modified by the BeforeAuthenticate() method. The array keys are the scope values and the array values are the corresponding display forms of the scope values.
-
properties is an instance of %OAuth2.Server.PropertiesOpens in a new tab that contains properties and claims received by the authorization server and modified by methods earlier in the processing. See Details for the %OAuth2.Server.Properties Object.
-
loginCount is the integer count of which login attempt is taking place.
This method is responsible for writing the HTML to display the user login form. The login form must contain a Username field, a Password field, and an AuthorizationCode field (which should be hidden). The default DisplayLogin() method uses of the InsertHiddenField() method of %CSP.PageOpens in a new tab to add the AuthorizationCode hidden field.
Typically, the form also has buttons with the values Login and Cancel. These buttons should submit the form. If the user submits the form with the Login button, the method will accept the username and password. If the user submits the form with the Cancel button, the authorization process will terminate with an error return of access_denied.
In your implementation, you might choose to display permissions on the same page. In that case, your method would display the scopes and would use a button named Accept to submit the page.
The method must return a %StatusOpens in a new tab.
Updating properties.CustomProperties
If the form contains elements with names that start p_, such elements receive special handling. After the DisplayLogin() method returns, InterSystems IRIS adds values of those elements to the properties.CustomProperties array, first removing the p_ prefix from the names. For example, if the form contains an element named p_addme, then InterSystems IRIS adds addme (and the value of the p_addme element) to the properties.CustomProperties array.
Your method can also directly set other properties of properties as needed.
Validating the User and Specifying Claims
The information here applies to all grant types other than the client credentials grant type. (For that grant type, see Validating the Client.)
To validate the user and specify any claims to be returned by the token, Userinfo, and token introspection endpoints, define the ValidateUser() method of the Validate user class. This method has the following signature:
ClassMethod ValidateUser(username As %String,
password As %String,
scope As %ArrayOfDataTypes,
properties As %OAuth2.Server.Properties,
Output sc As %Status) As %Boolean
Where:
-
username is the username provided by the user.
-
password is the password provided by the user. Note that if the user has already logged in, InterSystems IRIS calls this method with password as an empty string. This means that your method should detect when password is an empty string and not attempt to check the password in that case.
-
scope is an instance of %ArrayOfDataTypesOpens in a new tab that contains the scopes contained in the original client request, possibly modified by the BeforeAuthenticate() method. The array keys are the scope values and the array values are the corresponding display forms of the scope values.
-
properties is an instance of %OAuth2.Server.PropertiesOpens in a new tab that contains properties and claims received by the authorization server and modified by methods earlier in the processing. See Details for the %OAuth2.Server.Properties Object.
-
sc is the status code set by this method. Use this to communicate details of any errors.
Your method should do the following:
-
Make sure that the password applies to the given username.
-
Use the scope and properties arguments as needed for your business needs.
-
Modify the properties object to specify any claim values, as needed, or to add new claims. For example:
// Setup claims for profile and email OpenID Connect scopes.
Do properties.SetClaimValue("sub",username)
Do properties.SetClaimValue("preferred_username",username)
Do properties.SetClaimValue("email",email)
Do properties.SetClaimValue("email_verified",0,"boolean")
Do properties.SetClaimValue("name",fullname)
-
In the case of any errors, set the sc variable.
-
Return 1 if the user is considered valid; return 0 in all other cases.
Note that after the return from ValidateUser(), the authorization server automatically sets the following values in the properties object, if these values are missing:
Displaying Permissions
The information here applies only to the authorization code and implicit grant types.
To display permissions after validating the user, implement the DisplayPermissions() method of the Authenticate class. This method has the following signature:
ClassMethod DisplayPermissions(authorizationCode As %String,
scopeArray As %ArrayOfDataTypes,
currentScopeArray As %ArrayOfDataTypes,
properties As %OAuth2.Server.Properties) As %Status
Where:
-
authorizationCode is the authorization code.
-
scopeArray represents the newly requested scopes, for which the user has not yet granted permission. This argument is an instance of %ArrayOfDataTypesOpens in a new tab.
The array keys are the scope values and the array values are the corresponding display forms of the scope values.
-
currentScopeArray represents the scopes for which the user has previously granted permission. This argument is an instance of %ArrayOfDataTypesOpens in a new tab.
The array keys are the scope values and the array values are the corresponding display forms of the scope values.
-
properties is an instance of %OAuth2.Server.PropertiesOpens in a new tab that contains properties and claims received by the authorization server and modified by methods earlier in the processing. See Details for the %OAuth2.Server.Properties Object.
This form must have buttons with the values Accept and Cancel. These buttons should submit the form. If the user submits the form with the Accept button, the method should continue with authorization. If the user submits the form with the Cancel button, the authorization process should terminate.
Optional Custom Processing After Authentication
The information here applies to all grant types.
To perform custom processing after authentication, implement the AfterAuthenticate() method of the Authenticate class. This method has the following signature:
ClassMethod AfterAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
Where:
In your method, optionally modify either or both of these arguments. In particular, you may want to may add properties to the authentication HTTP response; to do so add properties to properties.ResponseProperties.
Normally, there is no need to implement this method. However, one use case is to implement the launch and launch/patient scopes used by FHIR®, where it is necessary to adjust the scope to include a specific patient.
Generating the Access Token
The information here applies to all grant types.
To generate access tokens, implement the GenerateAccessToken() method of the Generate token class. This method has the following signature:
ClassMethod GenerateAccessToken(properties As %OAuth2.Server.Properties, Output sc As %Status) As %String
Where:
The method should return the access token. The access token may be based on the properties argument. In your method, you might also want to add claims to the JSON response object. To do so, set the ResponseProperties array property of the properties object.
Validating the Client
The information here applies only to the client credentials type.
To validate the client credentials and specify any claims to be returned by the token, Userinfo, and token introspection endpoints, define the ValidateClient() method of the Validate user class. This method has the following signature:
ClassMethod ValidateClient(clientId As %String,
clientSecret As %String,
scope As %ArrayOfDataTypes,
Output properties As %OAuth2.Server.Properties,
Output sc As %Status) As %Boolean
Where:
-
clientId is the client ID.
-
clientSecret is the client secret.
-
scope is an instance of %ArrayOfDataTypesOpens in a new tab that contains the scopes contained in the original client request, possibly modified by the BeforeAuthenticate() method. The array keys are the scope values and the array values are the corresponding display forms of the scope values.
-
properties is an instance of %OAuth2.Server.PropertiesOpens in a new tab that contains properties and claims received by the authorization server and modified by methods earlier in the processing. See Details for the %OAuth2.Server.Properties Object.
-
sc is the status code set by this method. Use this to communicate details of any errors.
Your method should do the following:
-
Make sure that the client secret applies to the given client ID.
-
Use the scope and properties arguments as needed for your business needs.
-
Modify the properties object to specify any claim values, as needed. For example:
// Setup claims for profile and email OpenID Connect scopes.
Do properties.SetClaimValue("sub",username)
Do properties.SetClaimValue("preferred_username",username)
Do properties.SetClaimValue("email",email)
Do properties.SetClaimValue("email_verified",0,"boolean")
Do properties.SetClaimValue("name",fullname)
-
In the case of any errors, set the sc variable.
-
Return 1 if the user is considered valid; return 0 in all other cases.
Note that after the return from ValidateClient(), the authorization server automatically sets the following values in the properties object, if these values are missing:
Details for the %OAuth2.Server.Properties Object
The methods described in the previous section use the argument properties, which is an instance of %OAuth2.Server.PropertiesOpens in a new tab. The %OAuth2.Server.PropertiesOpens in a new tab class is intended to hold information that needs to be passed from method to method within the authorization server code. This section describes the basic properties in this class, as well as the properties related to claims. The class also has methods for working with claims; the last subsection describes them.
Basic Properties
The %OAuth2.Server.PropertiesOpens in a new tab class has the following basic properties, used to convey information for any internal processing of your custom code:
RequestProperties
Property RequestProperties as array of %String (MAXLEN=16384);
Contains the query parameters from the authorization request.
Because this property is an array, use the usual array interface to work with it. (The same comment applies to the other properties of this class.) For example, to get the value of a query parameter, use RequestProperties.GetAt(parmname), where parmname is the name of the query parameter.
ResponseProperties
Property ResponseProperties as array of %String (MAXLEN=1024);
Contains any properties to be added to the JSON response object to a token request. Set this property as needed.
CustomProperties
Property CustomProperties as array of %String (MAXLEN=1024);
Contains any custom properties to be used to communicate between various pieces of customization code. See Updating properties.CustomProperties.
ServerProperties
Property ServerProperties as array of %String (MAXLEN=1024);
Contains any properties that the authorization server chooses to share with the customization code. The logo_uri, client_uri, policy_uri and tos_uri client properties are shared in this way for use by the Authentication Class.
Properties Related to Claims
The %OAuth2.Server.PropertiesOpens in a new tab class contains the IntrospectionClaims, IDTokenClaims, UserinfoClaims, and JWTClaims properties, which carry information about required claims, specifically custom claims.
The class also contains the ClaimValues property, which carries the actual claim values. Your customization code should set the values of the claims (typically in the ValidateUser class).
The following list describes these properties:
IntrospectionClaims
Property IntrospectionClaims as array of %OAuth2.Server.Claim;
Specifies the claims to be returned by the Introspection endpoint (beyond the base required claims). The authorization server will return the scope, client_id, username, token_type, exp, iat, nbf, sub, aud, iss, and jti claims even if they are not in this property.
In most cases, the value of this property can be an empty string; this property is included to support the claims request parameter (see OpenID Connect CoreOpens in a new tab section 5.5 for details).
Formally, this property is an array in which the array key is the claim name (which matches the name in the ClaimValues property) and the array value is an instance of %OAuth2.Server.ClaimOpens in a new tab. The %OAuth2.Server.ClaimOpens in a new tab class has the following properties:
The value of the claims will usually be set by the ValidateUser class.
IDTokenClaims
Property IDTokenClaims as array of %OAuth2.Server.Claim;
Specifies the claims that the authorization server requires in the IDToken (beyond the base set of required claims). The authorization server requires the iss, sub, exp, aud, and azp claims even if these claims are not in this property.
This property is an array of objects; for details, see the entry for the IntrospectionClaims property.
In most cases, the value of this property can be an empty string; this property is included to support the claims request parameter (see OpenID Connect CoreOpens in a new tab section 5.5 for details).
UserinfoClaims
Property UserinfoClaims as array of %OAuth2.Server.Claim;
Specifies the claims to be returned by the Userinfo endpoint (beyond the base required claims). The authorization server will return the sub claim even if that claim is not in this property.
In most cases, the value of this property can be an empty string; this property is provided to support section 5.5 of OpenID Connect Core.
This property is an array of objects; for details, see the entry for the IntrospectionClaims property.
The claims are defined based on the scope and request claims parameter. The value to be returned for the claim will have the same key in the ClaimValues property. The value of the claims will usually be set by the ValidateUser class.
JWTClaims
Property JWTClaims as array of %OAuth2.Server.Claim;
Specifies the claims that are needed for the JWT access token that is returned by the default JWT-based access token class (%OAuth2.Server.JWTOpens in a new tab) beyond the base set of required claims. The authorization server will return the iss, sub, exp, aud, and jti claims even if they are not in this property.
This property is an array of objects; for details, see the entry for the IntrospectionClaims property.
The claims are defined by the customization code. The value of the claims will usually be set by the ValidateUser class.
ClaimValues
property ClaimValues as array of %String(MAXLEN=1024);
Specifies the actual claim values and their types. To work with this property, use the methods in the next section.
If you need to work with this property directly, note that this property is an array in which:
-
The array key is the claim name.
-
The array value has the form $LISTBUILD(type,value), where type holds the type of the value, and value holds the actual value. The type can be "string", "boolean", "number", or "object". If type is "object", then value is a JSON object serialized as a string.
Note that value can be a $LIST structure. In this case, when the claim value is serialized, it is serialized as a JSON array, in which each array item has the given type.
Methods for Working with Claims
The %OAuth2.Server.PropertiesOpens in a new tab class also provides instance methods that you can use to work with that simplify working with the ClaimValues property.
SetClaimValue()
Method SetClaimValue(name As %String, value As %String, type As %String = "string")
Updates the ClaimValues property by setting the value of the claim named by the name argument. The type argument indicates the type of the claim: "string" (the default) , "boolean", "number", or "object". If type is "object", then value must be a JSON object serialized as a string.
Note that value can be a $LIST structure. In this case, when the claim value is serialized, it is serialized as a JSON array, in which each array item has the given type.
RemoveClaimValue()
Method RemoveClaimValue(name As %String)
Updates the ClaimValues property by removing the claim named by the name argument.
GetClaimValue()
Method GetClaimValue(name As %String, output type) As %String
Examines the ClaimValues property and returns the value of the claim named by the name argument. The type argument, which is returned as output, indicates the type of the claim; see SetClaimValue().
NextClaimValue()
Method NextClaimValue(name As %String) As %String
Returns the name of the next claim (in the ClaimValues property) after the given claim.
Rotating Keys Used for JWTs
In most cases, you can cause the authorization server to generate new public/private key pairs; this applies only to the RSA keys used for the asymmetric RS256, RS384, and RS512 algorithms. (The exception is if you specify Source other than dynamic registration as X509 certificate. In this case, it is not possible to generate new keys.)
Generating new public/private key pairs is known as key rotation; this process adds new private RSA keys and associated public RSA keys to the private and public JWKSs.
When you perform key rotation on the authorization server, the authorization server uses the new private RSA keys to sign JWTs to be sent to the clients. Similarly, the authorization server uses the new public RSA keys to encrypt JWTs to be sent to the clients. To decrypt JWTs received from the clients, the authorization server uses the new RSA keys, and if that fails, uses the old RSA keys; thus the server can decrypt a JWT that was created using its old public RSA keys.
Last, if the authorization server cannot verify a signed JWT received from a client, then if the authorization server has the URL for the client public JWKS, the authorization server obtains a new public JWKS and tries again to verify the signature. (Note that the authorization server has a URL for the client public JWKS if you used dynamic discovery or if the configuration specified the JWKS from URL option; otherwise, the authorization server does not have this URL.)
To rotate keys for the authorization server:
-
Select System Administration > Security > OAuth 2.0 > Server Configuration.
The system displays the configuration for the authorization server.
-
Select the Rotate Keys button.
Note:
The symmetric HS256, HS384, and HS512 algorithms always use the client secret as the symmetric key.
Getting a New Public JWKS from a Client
In most cases, a client generates a public/private pair of JWKSs. There are different ways in which the authorization server can receive the public JWKS. One way is for the client to provide the public JWKS at a URL; see the JWKS from URL option in Creating Client Definitions on an InterSystems IRIS OAuth 2.0 Authorization Server.
If the client was defined with JWKS from URL and if the client generates a new pair of JWKSs, you can cause the authorization server to obtain the new public JWKS from the same URL. To do so:
-
In the Management Portal, select System Administration > Security > OAuth 2.0 > Server Configuration.
The system displays the configuration for the authorization server.
-
Select the Update JWKS button.
If the client was not defined with JWKS from URL and if the client generates a new pair of JWKSs, it is necessary to obtain the public JWKS, send it to the authorization server, and load it from a file.