Using OAuth 2.0 and OpenID Connect with Caché
Using Caché as an OAuth 2.0 Authorization Server
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
Search:    

This chapter describes how to use a Caché instance as an OAuth 2.0 authorization server. It discusses the following:

Note that this chapter is organized differently from the other chapters in this book. 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 chapter.
Configuration Requirements for the Caché Authorization Server
To use a Caché instance as an OAuth 2.0 authorization server, perform the following configuration tasks:
Configuring the Authorization Server
In order to perform this task, you must be logged in as a user who has USE permission on the %Admin_Secure resource.
  1. In the Management Portal, select System Administration > Security > OAuth 2.0 > Server Configuration.
  2. On the General tab, specify the following details:
  3. On the Scopes tab, specify the following details:
  4. On the Intervals tab, specify the following details:
  5. For the JWT Settings tab, specify the following details:
  6. On the Customization tab, specify details as described in Code Customization Options,” later in this chapter.
  7. Select Save.
When you save this configuration, the system creates a web application (/oauth2) for use by the authorization server. Do not modify this web application.
Code Customization Options and Overall Flow
This section describes the items in the Customization Options section of the configuration options of the authorization server. Subsections describe the overall flow and the default classes.
If you use any custom subclasses, see Implementing the Custom Methods.”
How a Caché Authorization Server Processes Requests
This section describes what a Caché authorization server does when it receives an authorization code request or an implicit request for a token.
  1. Calls the BeforeAuthenticate() method of the class specified via the Authenticate class option. The purpose of this method is to make any modifications to the request before user identification starts.
    In the default class, this method is a stub.
  2. Next, if the grant type is authorization code or implicit grant, Caché does the following:
    1. Calls the DisplayLogin() of the class specified via the Authenticate class option. (But also see the appendix Implementing DirectLogin().”)
      In the default class, DisplayLogin() displays a simple HTML login page.
    2. If the username is not null, calls the ValidateUser() method of the class specified via the Validate user class option. The purpose of this method is to validate the user and (by modifying the properties array) to prepare any claims to be returned by the token, Userinfo, and token introspection endpoints.
      In the default class, this method is only a sample and is very unlikely to be suitable for production use.
    3. If the user is validated, calls the DisplayPermissions() method of the class specified via the Authenticate class option. The purpose of this method is to display a page to the user that lists the requested permissions.
      In the default class, this method displays a simple HTML page with the permissions.
    Or if the grant type is password credentials, Caché just calls the ValidateUser() method of the class specified via the Validate user class option.
    Or if the grant type is client credentials, Caché just calls the ValidateClient() method of the class specified via the Validate user class option.
  3. If the user accepts the permissions, calls the AfterAuthenticate() method of the class specified via the Authenticate class option. The purpose of this method is to perform any custom processing before generating an access token.
    In the default class, this method is a stub.
  4. Calls the GenerateAccessToken() method of the class specified via the Generate token class option. The purpose of this method is to generate an access token to return to the user.
    In the default class (%OAuth2.Server.Generate), this method generates an access token that is an opaque string. Caché also provides an alternative class (%OAuth2.Server.JWT), in which GenerateAccessToken() generates an access token that is a JWT.
Default Classes
This section describes the default classes in a Caché authorization server, as well as the class %OAuth2.Server.JWT, which is provided as another option for the Generate token class.
%OAuth2.Server.Authenticate (Default for Authenticate Class)
The class %OAuth2.Server.Authenticate defines the following methods, listed in the order in which they are called:
%OAuth2.Server.Validate (Default for Validate User Class)
The %OAuth2.Server.Validate class is the default class for the Validate user class option.
Note:
This class is provided for sample purposes and is very unlikely to be suitable for production use. That is, InterSystems expects that customers will replace or subclass this class for their own needs.
This class defines the following sample methods:
You can override all these methods in your subclass.
OAuth2.Server.Session (Default for Session Class)
The %OAuth2.Server.Session class is the default class for the Session maintenance class option. This class maintains sessions via an HTTP-only cookie.
In this class, the GetUser() method tries to access the current session. If there is a session, the method obtains the username from that session and returns that. If there is no session, the method returns the username as an empty string and also returns an error status as output.
For additional information on this class, see the class reference.
%OAuth2.Server.Generate (Default for Generate Token Class)
The %OAuth2.Server.Generate class is the default class for the Generate token class option. This class defines the following methods:
%OAuth2.Server.JWT (Another Option for Generate Access Token Class)
The %OAuth2.Server.JWT class is another class you can use (or subclass) for the Generate token class option. This class defines the following methods:
Implementing the Custom Methods for the Caché 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.
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 %Status.
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:
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.Page 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 %Status.
Updating properties.CustomProperties
If the form contains elements with names that start p_, such elements receive special handling. After the DisplayLogin() method returns, Caché 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 Caché 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:
Your method should do the following:
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:
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:
Your method should do the following:
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.Properties. The %OAuth2.Server.Properties 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.Properties 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 the section 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.Properties 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 Core 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.Claim. The %OAuth2.Server.Claim 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 Core 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.JWT) 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:
Methods for Working with Claims
The %OAuth2.Server.Properties 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.
Locations of the Authorization Server Endpoints
When you use a Caché instance as an OAuth 2.0 authorization server, the URLs for the authorization endpoints are as follows:
Endpoint URL
Issuer endpoint https://serveraddress/oauth2
Authorization endpoint https://serveraddress/oauth2/authorize
Token endpoint https://serveraddress/oauth2/token
Userinfo endpoint https://serveraddress/oauth2/userinfo
Token introspection endpoint https://serveraddress/oauth2/introspection
Token revocation endpoint https://serveraddress/oauth2/revocation
In all cases, serveraddress is the IP address or host name of the server on which the Caché instance is running.
Creating Client Definitions on a Caché OAuth 2.0 Authorization Server
This section describes how to create a client definition on a Caché OAuth 2.0 authorization server, if you have not registered the client dynamically. First, set up the Caché OAuth 2.0 authorization server as described earlier in this chapter. Then use the Management Portal to do the following:
  1. Click the Client Configurations button to view the client descriptions. This table is initially empty.
  2. On the General tab, specify the following details:
  3. If needed, select the Client Credentials tab and view the following details:
  4. On the Client Information tab, specify the following details:
  5. On the JWT Settings tab, specify the following details:
  6. Select Save.
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:
  1. The system displays the configuration for the authorization server.
  2. Select the Rotate Keys button.
Note:
The symmetric HS256, HS384, and HS512 algorithms always use the client secret as the symmetric key.
API for Key Rotation on the Authorization Server
To rotate keys programmatically on the authorization server, call the RotateKeys() method of OAuth2.Server.Configuration.
To obtain a new client JWKS, call the UpdateJWKS() method of OAuth2.Server.Client.
For details on these methods, see the class reference.
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 a Caché 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:
  1. In the Management Portal, select System Administration > Security > OAuth 2.0 > Server Configuration.
    The system displays the configuration for the authorization server.
  2. 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.