Skip to main content

Using a Caché Web Application as an OAuth 2.0 Client

This chapter describes how to use a Caché web application as a client application that uses the OAuth 2.0 framework. It discusses the following:

This chapter primarily discusses the scenario in which a Caché web application is the client of a web server/client application and uses the authorization code grant type. See the last section for details on other grant types and other variations.

Prerequisites for the Caché Client

Before starting the tasks described in this chapter, make sure the following items are available:

  • An OAuth 2 authorization server. Later you will need to know specific details about this server. Some of the details apply when you configure the client within Caché:

    • Location of the authorization server (issuer endpoint)

    • Location of the authorization endpoint

    • Location of the token endpoint

    • Location of the Userinfo endpoint (if supported; see OpenID Connect CoreOpens in a new tab)

    • Location of the token introspection endpoint (if supported; see RFC 7662Opens in a new tab)

    • Location of the token revocation endpoint (if supported; see RFC 7009Opens in a new tab)

    • Whether the authorization server supports dynamic registration

    Other details apply when you write the client code:

    • Grant types supported by this server

    • Scopes supported by this server. For example, the server may or may not support openid and profile, which are special scopes defined by OpenID Connect CoreOpens in a new tab.

    • Other requirements for requests made to this server

  • If the authorization server does not support dynamic client registration, the Caché application must be registered as a client of the OAuth 2.0 authorization server, and you must have the client ID and client secret for this client. The details depend upon the implementation of the authorization server. (If the server does support dynamic registration, you can register the client while configuring it as described in this chapter.)

Configuration Requirements

To use a Caché web application as an OAuth 2.0 client, perform the following configuration tasks:

  • For the web server that is serving Caché, configure that web server to use SSL. It is beyond the scope of this documentation to describe how to configure a web server to use SSL.

  • Create a Caché SSL configuration for use by the client.

    This should be a client SSL configuration; no certificate is needed. The configuration is used to connect to a web server. Via this connection, the client communicates with the authorization server to obtain access tokens, call the Userinfo endpoint, call the introspection endpoint, and so on.

    For details on creating SSL configurations, see the chapter “Using SSL/TLS with Caché” in the Caché Security Administration Guide.

    Each SSL configuration has a unique name. For reference, the documentation refers to this one as sslconfig, but you can use any unique name.

  • Create the OAuth 2.0 configuration items for the client. To do so, first create the server description and then create the client configuration, as described in the subsections.

    For both items, to find the needed options in the Management Portal, select System Administration > Security > OAuth 2.0 > Client Configuration. This page provides the options needed when you create an OAuth 2.0 configuration on a client machine (that is, on any machine other than one being used as an authorization server).

    On a client machine, do not use the menu System Administration > Security > OAuth 2.0 > Server Configuration.

Creating a Server Description (Using Discovery)

  1. In the Management Portal, select System Administration > Security > OAuth 2.0 > Client Configuration.

    This displays a page that lists any server descriptions that are available on this instance. In any given row, the Issuer endpoint column indicates the issuer endpoint for the server description. The Client Count column indicates the number of client configurations associated with the given server description. In the last column, the Client Configurations link enables you to create, view, edit, and delete the associated client configurations.

  2. Select Create Server Configuration.

    The Management Portal then displays a new page where you can enter details for the server description.

  3. Specify the following details:

    • Issuer endpoint (required) — Enter the endpoint URL to be used to identify the authorization server.

    • SSL/TLS configuration (required) — Select the SSL/TLS configuration to use when making the dynamic client registration request.

    • Registration access token — Optionally enter the initial registration access token to use as a bearer token to authorize the dynamic client registration request.

  4. Select Discover and Save.

    Caché then communicates with the given authorization server, retrieves information needed in the server description, and then saves that information.

    The Management Portal then redisplays the list of server descriptions.

Manually Creating a Server Description (No Discovery)

To manually create a server description (rather than using discovery), first display the server description page (steps 1 and 2 above) and then select Manual. Then the page displays a larger set of options, as follows:

  • Issuer endpoint (required) — Enter the endpoint URL to be used to identify the authorization server.

  • Authorization endpoint (required) — Enter the endpoint URL to be used when requesting an authorization code from the authorization server.

  • Token endpoint (required) — Enter the endpoint URL to be used when requesting an access token from the authorization server.

  • Userinfo endpoint — Enter the endpoint URL to be used when making a Userinfo request using an access token from the authorization server for authorization.

  • Token introspection endpoint — Enter the endpoint URL to be used when making a token introspection request using the client_id and client_secret for authorization. See RFC 7662Opens in a new tab.

  • Token revocation endpoint— Enter the endpoint URL to be used when making a token revocation request using the client_id and client_secret for authorization. See RFC 7009Opens in a new tab.

  • JSON Web Token (JWT) Settings — Specifies the source of the public keys that the client should use for signature verification and decryption of JWTs from the authorization server.

    By default, the authorization server generates a pair of JWKSs (JSON web key sets). One JWKS is private and contains all the needed private keys (per algorithm) as well as the client secret for use as a symmetric key; this JWKS is never shared. The other JWKS contains the corresponding public keys and is publicly available. The process of creating the server definition also copies the public JWKS from the authorization server to the client for its use in signature verification and encryption of JWTs.

    To access any of these options, first select Source other than dynamic registration.

Specify these values and then select Save.

Configuring and Dynamically Registering a Client

This section describes how to create a client configuration and dynamically register the client.

  1. In the Management Portal, select System Administration > Security > OAuth 2.0 > Client Configuration.

    The Management Portal displays the list of server descriptions.

  2. Click the Client Configurations link in the row for the server description with which this client configuration should be associated.

    The Management Portal then displays the list of client configurations associated with the server description. This list is initially empty.

  3. Click Create Client Configuration.

    The Management Portal then displays a new page where you can enter details.

  4. On the General tab, specify the following details:

    • Application name — Specify a short name for the application.

    • Client name — Specify the client name to display to the end user.

    • Description — Specify an optional description of the application.

    • Enabled — Optionally clear this check box if you want to prevent this application from being used.

    • Client Type — Select one of the following:

      • Confidential — Specifies that the client is a confidential client, per RFC 6749Opens in a new tab.

        This chapter primarily discusses the scenario in which the client uses the authorization code grant type. For this scenario, specify Client Type as Confidential. For other grant types, see “Variations,” later in this chapter.

      • Public — Specifies that the client is a public client, per RFC 6749Opens in a new tab.

      • Resource Server — Specifies that the client is a resource server which is not also a client.

    • SSL/TLS configuration — Select the SSL configuration you created for use by the client (for example, sslconfig).

    • The client URL to be specified to the authorization server to receive responses — Specify the URL of the internal destination required for a Caché OAuth 2.0 client. At this destination, the access token is saved and then the browser is further redirected back to the client application.

      To specify this URL, enter values for the following options:

      • Host name — Specify the host name or IP address of the authorization server.

      • Port — Specify this if needed to accommodate any changes in the CSP Gateway configuration.

      • Prefix — Specify this if needed to accommodate any changes in the CSP Gateway configuration.

      The resulting URL has the following form:

      https://hostname:port/prefix/csp/sys/oauth2/OAuth2.Response.cls
      

      If you omit Port, the colon is omitted. Similarly, if you omit Prefix, there is only one slash between hostname:port and csp. (Also if the Use TLS/SSL option is cleared, the URL starts with http rather than https.)

    • Use TLS/SSL — Select this option, unless there is a good reason not to use TLS/SSL when opening the redirect page.

    • Required grant types — Specify the OAuth 2.0 grant types that the client will restrict itself to using.

    • Authentication type — Select the type of authentication (as specified in RFC 6749Opens in a new tab or OpenID Connect Core section 9Opens in a new tab) to be used for HTTP requests to the authorization server. Select one of the following: none, basic, form encoded body, client secret JWT, or private key JWT.

    • Authentication signing algorithm — Select the algorithm that must be used for signing the JWTs used to authenticate this client at the token endpoint (if the authentication type is client secret JWT or private key JWT). If you do not select an option, any algorithm supported by the OpenID provider and the relying party may be used.

  5. On the Client Information tab, specify the following details:

    • Logo URL — URL of the logo for the client application.

    • Client home page URL — URL of the home page for the client application.

    • Policy URL — URL of the policy document for the client application.

    • Terms of service URL — URL of the terms of service document for the client application.

    • Default scope — Specify the default scope, as a blank separated list, for access token requests. This default should be consistent with the scopes permitted by the authorization server.

    • Contact emails — Comma-separated list of email addresses suitable for use in contacting those responsible for the client application.

    • Default max age — Specify the default maximum authentication age, in seconds. If you specify this option, the end user must be actively re-authenticated when the maximum authentication age is reached. The max_age request parameter overrides this default value. If you omit this option, there is no default maximum authentication age.

  6. On the JWT Settings tab, specify the following details:

    • Create JWT Settings from X509 credentials — Select this option if, for signing and encryption, you want to use the private key associated with a certificate; in this case, also see “Using Certificates for an OAuth 2.0 Client,” in the appendix “Certificates and JWTs (JSON Web Tokens).”

      Note:

      InterSystems expects that the option Create JWT Settings from X509 credentials will rarely be used, and that instead customers use the default behavior described next.

      If you leave this option clear, the system generates a pair of JWKSs (JSON web key sets). One JWKS is private and contains all the needed private keys (per algorithm) as well as the client secret for use as a symmetric key; this JWKS is never shared. The other JWKS contains the corresponding public keys and is publicly available. The dynamic registration process also copies the public JWKS to the authorization server, so that the authorization server can encrypt and verify signatures of JWTs from this client.

    • Signing algorithm — Select the signing algorithm used to create signed JWTs. Or leave this blank if JWTs are not to be signed.

    • Encryption algorithm — Select the encryption algorithm used to create encrypted JWTs. Or leave this blank if JWTs are not to be encrypted. If you select a value, you must also specify Key algorithm.

    • Key algorithm — Select the key management algorithm used to create encrypted JWTs. Or leave this blank if JWTs are not to be encrypted.

  7. If the authorization server supports dynamic registration, double-check all the data you have entered and then press Dynamic Registration and Save. Caché then contacts the authorization server, registers the client, and obtains the client ID and client secret.

    If the authorization server does not support dynamic registration, see the following subsection.

Configuring a Client (No Dynamic Registration)

If the authorization server does not support dynamic registration, then do the following instead of the last step above:

  1. Select the Client Credentials tab and specify the following details:

    • Client ID — Enter the client ID as provided by the authorization server.

    • Client secret — Enter the client secret as provided by the authorization server. This value is required if the Client Type is Confidential.

      This chapter primarily discusses the scenario in which the client uses the authorization code grant type. For this scenario, specify a value for Client secret. For other grant types, see “Variations,” later in this chapter.

    Do not enter values for Client ID Issued At, Client Secret Expires At, and Registration Client Uri.

  2. Select Save.

Outline of Code Requirements

Note:

This section describes the code needed when the client uses the authorization code grant type when requesting tokens. For other grant types, see “Variations,” later in this chapter.

In order for a Caché web application to act as OAuth 2.0 client, this web application must use logic like the following:

  1. Obtain an access token (and if needed, and ID token). See “Obtaining Tokens.”

  2. Examine the access token and (optionally, an ID token) to determine whether the user has the necessary permissions to use the requested resource. See “Examining the Tokens,” later in this chapter.

  3. If appropriate, call the resource server as described in “Adding an Access Token to an HTTP Request,” later in this chapter.

The following sections provide information on these steps.

Note that the pages of this web application must ultimately be based on %CSP.PageOpens in a new tab; you can create them with CSP, Zen, or Zen Mojo. It is useful to remember that all these kinds of pages can execute code on the client or on the server. The Caché OAuth 2.0 client API is a set of methods that run on the server.

Obtaining Tokens

Note:

This section provides information on the code needed when the client uses the authorization code grant type when requesting tokens. For other grant types, see “Variations,” later in this chapter.

To obtain tokens, use steps like the following to obtain tokens. The subsection provides details on the methods discussed here.

  1. Call the IsAuthorized() method of the %SYS.OAuth2.AccessTokenOpens in a new tab class. For this, you will need to first determine the desired scope or scopes for the access token.

    For example:

     set myscopes="openid profile scope1 scope2"
     set isAuth=##class(%SYS.OAuth2.AccessToken).IsAuthorized("myclient",,myscopes,
                     .accessToken,.idtoken,.responseProperties,.error)
    

    This method checks to see whether an access token has already been saved locally.

  2. Check to see if the error argument has returned an error and then handle that error appropriately. Note that if this argument contains an error, the function $ISOBJECT() will return 1; otherwise $ISOBJECT() will return 0.

        if $isobject(error) {
       //error handling here
     }
  3. If IsAuthorized() returns 1, skip to “Examining the Tokens.”

  4. Otherwise, call the GetAuthorizationCodeEndpoint() method of the %SYS.OAuth2.AuthorizationOpens in a new tab class. For this, you will need the following information:

    • Complete URL that the authorization server should redirect to, after it returns an access token. This is the client’s redirect page (which can be the same as the original page, or can be different).

    • The scope or scopes of the request.

    • Any parameters to be included with the request. For example, you may need to pass the claims parameter.

    For example:

     set scope="openid profile scope1 scope2"
     set redirect="http://localhost/csp/openid/SampleClientResult.csp"
    
     set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint("myclient",
           scope,redirect,.properties,.isAuthorized,.sc)
     if $$$ISERR(sc) {
       //error handling here
     }

    This method returns the full URL, including query parameters, of the internal destination required for a Caché OAuth 2.0 client.

  5. Provide an option (such as a button) that opens the URL returned by GetAuthorizationCodeEndpoint(), thus enabling the user to authorize the request.

    At this internal URL, which is never visible to users, Caché obtains an authorization code, exchanges that for an access token, and then redirects the browser to the client’s redirect page.

Method Details

This subsection provides the details on the methods described in the previous subsection.

IsAuthorized()

Location: This method is in the class %SYS.OAuth2.AccessTokenOpens in a new tab.

ClassMethod IsAuthorized(applicationName As %String, 
                         sessionId As %String, 
                         scope As %String = "", 
                         Output accessToken As %String, 
                         Output IDToken As %String, 
                         Output responseProperties, 
                         Output error As %OAuth2.Error) As %Boolean

This method returns 1 if there is a locally stored access token for this client and this session, and if that access token authorizes all the scopes given by the scope argument. (Note that this method looks for the access token in the CACHESYS database, and that tokens are removed automatically after they have expired.)

Otherwise the method returns 0.

The arguments are as follows:

  • applicationName is the name of the client application.

  • sessionId specifies the session ID. Specify this only if you want to override the default session (%session.SessionId).

  • scope is a space-delimited list of scopes, for example: "openid profile scope1 scope2"

    Note that openid and profile are special scopes defined by OpenID Connect CoreOpens in a new tab.

  • accessToken, which is returned as output, is the access token, if any.

  • IDToken, which is returned as output, is the ID token, if any. (This applies only if you are using OpenId ConnectOpens in a new tab, specifically if the request used the scope openid.) Note that an ID token is a JWT.

  • responseProperties, which is returned as output, is a multidimensional array that contains any parameters of the response. This array has the following structure:

    Array node Array value
    responseProperties(parametername) where parametername is the name of a parameter (such as token_type or expires_in) Value of the given parameter.
  • error, which is returned as output, is either (when there is no error) an empty string or (in the case of error) an instance of %OAuth2.ErrorOpens in a new tab containing error information.

    %OAuth2.ErrorOpens in a new tab has three string properties: Error, ErrorDescription, and ErrorUri.

GetAuthorizationCodeEndpoint()

Location: This method is in the class %SYS.OAuth2.AuthorizationOpens in a new tab.

ClassMethod GetAuthorizationCodeEndpoint(applicationName As %String, 
                                         scope As %String, 
                                         redirectURL As %String, 
                                         ByRef properties As %String, 
                                         Output isAuthorized As %Boolean, 
                                         Output sc As %Status, 
                                         responseMode As %String
                                         sessionId As %String = "") As %String

This method returns the URL, with all needed query parameters, of the local, internal page that Caché uses to request the authorization code. (Note that this page is never visible to users.)

The arguments are as follows:

  • applicationName is the name of the client application.

  • scope is a space-delimited list of scopes for which access is requested, for example: "scope1 scope2 scope3"

    The default is determined by the client configuration for the given applicationName.

  • redirectURL is the full URL of the client’s redirect page, the page to which the authorization server should redirect the browser after returning the access token to the client.

  • properties, which is passed by reference, is a multidimensional array that contains any parameters to be added to the request. This array must have the following structure:

    Array node Array value
    properties(parametername) where parametername is the name of a parameter Value of the given parameter. The value can be a scalar value, an instance of a dynamic object, or the UTF-8 encoded serialized form of a dynamic object.

    Use a dynamic object if you want the request to include a parameter whose value is a JSON object; a scenario is the claims parameter that is defined by OpenID Connect. For details on dynamic objects, see Using JSON in Caché.

    To use the request or request_uri parameter, see the section “Passing Request Objects as JWTs.”

  • isAuthorized, which is returned as output, equals 1 if there is a locally stored access token for this client and this session (scope is not checked). This parameter equals 0 otherwise. There is no need to check this output argument, because we have just called the IsAuthorized() method.

  • sc, which is returned as output, contains the status code set by this method.

  • responseMode specifies the mode of the response from the authorization server. This can be "query" (the default), "fragment" or "form_post". The default is almost always appropriate.

  • sessionId specifies the session ID. Specify this only if you want to override the default session (%session.SessionId).

Also see “Variation: Performing the Redirect within OnPreHTTP.”

Examining the Token(s)

After the client receives an access token (and, optionally, an ID token), the client should perform additional checks to determine whether the user has the necessary permissions to use the requested resource. To perform this examination, the client can use the methods described here to obtain additional information.

Then examine the dynamic objects returned by reference from these methods. For example, GetIntrospection() returns a dynamic object that contains the claims made by the introspection endpoint. Use these claims as needed to determine whether to proceed.

ValidateJWT()

Location: This method is in the class %SYS.OAuth2.ValidationOpens in a new tab.

ClassMethod ValidateJWT(applicationName As %String, 
                        accessToken As %String,
                        scope As %String, 
                        aud As %String, 
                        Output jsonObject As %RegisteredObject, 
                        Output securityParameters As %String, 
                        Output sc As %Status) As %Boolean

Use this method only if the access token is a JWT (rather than an opaque token).

This method decrypts the JWT if necessary, validates the JWT, and creates an object (jsonObject) to contain the JWT properties. To validate the JWT, the method checks the audience (if aud is specified), issuer endpoint (must match that specified in server definition), and scope (if scope is specified). The method also makes sure the access token has not expired. Both signed and unsigned JWTs are accepted. If the JWT is signed, the method checks the signature.

This method returns 1 if the JWT is valid or returns 0 otherwise. It also returns several arguments as output.

The arguments are as follows:

  • applicationName is the name of the client application.

  • accessToken is the JWT to be validated.

  • scope is a space-delimited list of scopes, for example: "scope1 scope2 scope3"

    If scope is specified, the JWT must contain a scope claim that includes this scope.

  • aud specifies the audience that is using the token. If the token has an associated aud property (usually because the audience was specified when requesting the token), then aud is matched to the token audience. If aud is not specified, then no audience checking takes place.

  • jsonObject, which is returned as output, is a dynamic object that contains the claims in the JWT. This dynamic object contains properties such as aud, exp, iss, and so on. For details on dynamic objects, see Using JSON in Caché.

  • securityParameters, which is returned as output, is a multidimensional array that contains security information taken from the header, for optional additional use in verifying signatures, decrypting, or both.

    This array contains the following nodes:

    Node Value
    securityParameters("sigalg") The signature or MAC algorithm. Set only if the JWT is signed
    securityParameters("keyalg") The key management algorithm
    securityParameters("encalg") The content encryption algorithm

    The keyalg and encalg nodes are either both specified or both null.

  • sc, which is returned as output, contains the status code set by this method.

If this method returns success (1), examine jsonObject, and use the contained claims as needed to determine whether to allow access to the requested resource. Use securityParameters if needed.

Because the OAuth specification allows an application to accept both signed and unsigned JWTs, the ValidateJWT method does not reject an unsigned JWT. However, in many cases it is strongly recommended that your application implement stricter security by rejecting an unsigned JWT. You can determine whether the token passed into ValidateJWT was unsigned by inspecting the securityParameters array that is returned by the method. If securityParamters ("sigalg") was not set, the token was unsigned. For example, the following code determines whether the token was unsigned and rejects it if it was:

Set tInitialValidationPassed = ##class(%SYS.OAuth2.Validation).ValidateJWT(tClientName, tAccessToken, "", "", .tJsonObj,.tSecurityParams, .tValidateStatus)
// the “sigalg” subscript is set only if the JWT was signed
Set tIsTokenSigned = $Data(tSecurityParams("sigalg"))#2 
If 'tIsTokenSigned {
   $$$ThrowStatus($System.Status.Error($$$AccessDenied))
}
ValidateIDToken()

Location: This method is in the class %SYS.OAuth2.ValidationOpens in a new tab.

ClassMethod ValidateIDToken(applicationName As %String, 
                            IDToken As %String, 
                            accessToken As %String, 
                            scope As %String, 
                            aud As %String, 
                            Output jsonObject As %RegisteredObject, 
                            Output securityParameters As %String, 
                            Output sc As %Status) As %Boolean

This method validates the signed OpenID Connect ID token (IDToken) and creates an object (jsonObject) to contain the properties of the ID token. To validate the ID token, the method checks the audience (if aud is specified), endpoint (must match that specified in server definition), and scope (if scope is specified), and signature. The method also makes sure the ID token has not expired.

This method also validates the access token (accessToken) based on the at_hash property of the ID token.

This method returns 1 if the ID token is valid or returns 0 otherwise. It also returns several arguments as output.

The arguments are as follows:

  • applicationName is the name of the client application.

  • IDToken is the ID token.

  • accessToken is the access token.

  • scope is a space-delimited list of scopes, for example: "scope1 scope2 scope3"

  • aud specifies the audience that is using the token. If the token has an associated aud property (usually because the audience was specified when requesting the token), then aud is matched to the token audience. If aud is not specified, then no audience checking takes place.

  • jsonObject, which is returned as output, is a dynamic object that contains the properties of the IDToken. Note that an ID token is a JWT. For details on dynamic objects, see Using JSON in Caché.

  • securityParameters, which is returned as output, is a multidimensional array that contains security information taken from the header, for optional additional use in verifying signatures, decrypting, or both. See the securityParameters argument for ValidateJWT().

  • sc, which is returned as output, contains the status code set by this method.

If this method returns success (1), examine jsonObject, and use the contained claims as needed to determine whether to allow access to the requested resource. Use securityParameters if needed.

GetIntrospection()

Location: This method is in the class %SYS.OAuth2.AccessTokenOpens in a new tab.

ClassMethod GetIntrospection(applicationName As %String, 
                             accessToken As %String, 
                             Output jsonObject As %RegisteredObject) As %Status

This method sends the access token to the introspection endpoint, receives a response that contains claims, and creates an object (jsonObject) that contains the claims returned by that endpoint.

The request is authorized using the basic authorization HTTP header with the client_id and client_secret associated with applicationName.

The arguments are as follows:

  • applicationName is the name of the client application.

  • accessToken is the access token.

  • jsonObject, which is returned as output, is a dynamic object that contains the claims returned by introspection endpoint. For details on dynamic objects, see Using JSON in Caché.

Note that you cannot use this method if the server does not specify an introspection endpoint or if Client secret is not specified.

If this method returns success (1), examine jsonObject, and use the contained claims as needed to determine whether to allow access to the requested resource.

GetUserinfo()

Location: This method is in the class %SYS.OAuth2.AccessTokenOpens in a new tab.

ClassMethod GetUserinfo(applicationName As %String, 
                        accessToken As %String, 
                        IDTokenObject As %RegisteredObject, 
                        Output jsonObject As %RegisteredObject, 
                        Output securityParameters As %String) As %Status

This method sends the access token to the Userinfo endpoint, receives a response that contains claims, and creates an object (jsonObject) that contains the claims returned by that endpoint. If the response returns a JWT, then the response is decrypted and the signature is checked before jsonObject is created. If the argument IDTokenObject is specified, the method also verifies that the sub claim from the User info endpoint matches the sub claim in IDTokenObject.

The request is authorized using the specified access token.

The arguments are as follows:

  • applicationName is the name of the client application.

  • accessToken is the access token.

  • IDTokenObject (optional), is a dynamic object containing an ID token. For details on dynamic objects, see Using JSON in Caché.

  • jsonObject, which is returned as output, is a dynamic object that contains the claims returned by Userinfo endpoint.

  • securityParameters, which is returned as output, is a multidimensional array that contains security information taken from the header, for optional additional use in verifying signatures, decrypting, or both. See the securityParameters argument for ValidateJWT().

If this method returns success (1), examine jsonObject, and use the contained claims as needed to determine whether to allow access to the requested resource. Use securityParameters if needed.

Adding an Access Token to an HTTP Request

After the client application has received and examined an access token, the application can make HTTP requests to the resource server. Depending on the application, those HTTP requests may need the access token.

To add an access token to an HTTP request (as a bearer token HTTP authorization header), do the following:

  1. Create an instance of %Net.HttpRequestOpens in a new tab and set properties as needed.

    For details on this class, see “Sending HTTP Requests” in Using Caché Internet Utilities.

  2. Call the AddAccessToken() method of %SYS.OAuth2.AccessTokenOpens in a new tab, which adds the access token to the HTTP request. This method is as follows:

    ClassMethod AddAccessToken(httpRequest As %Net.HttpRequest, 
                               type As %String = "header", 
                               sslConfiguration As %String,  
                               applicationName As %String,  
                               sessionId As %String) As %Status
    

    This method adds the bearer access token associated with the given application and session to the resource server request as defined by RFC 6750Opens in a new tab. The arguments are as follows:

    • httpRequest is the instance of %Net.HttpRequestOpens in a new tab that you want to modify.

    • type specifies how to include the access token in the HTTP request:

      • "header" — Use the bearer token HTTP header.

      • "body" — Use form-encoded body. In this case, the request must be a POST with a form-encoded body.

      • "query" — Use a query parameter.

    • sslConfiguration is the Caché SSL configuration to use for this HTTP request. If you omit this, Caché uses the SSL configuration associated with the client configuration.

    • applicationName is the name of the client application.

    • sessionId specifies the session ID. Specify this only if you want to override the default session (%session.SessionId).

    This method returns a status code, which your code should check.

  3. Send the HTTP request (as described in “Sending HTTP Requests” in Using Caché Internet Utilities. To do so, you call a method such as Get() or Put().

  4. Check the status returned by the previous step.

  5. Optionally examine the HTTP response, which is available as the HttpResponse property of the HTTP request.

    See “Sending HTTP Requests” in Using Caché Internet Utilities.

For example:

 set httpRequest=##class(%Net.HttpRequest).%New()
 // AddAccessToken adds the current access token to the request.
 set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(httpRequest,,"sslunittest",applicationName)
 if $$$ISOK(sc) {
    set sc=httpRequest.Get("https://myresourceserver/csp/openid/openid.SampleResource.cls")
 }

Optionally Defining Delegated Authentication for the Web Client

You can optionally define delegated authentication for a Caché web client that is used as an OAuth 2.0 client. Caché provides two ways that you can do this:

  • By creating and using a ZAUTHENTICATE routine, starting from a sample that is provided for use with OAuth 2.0. Your client code must also call %session.Login().

  • By creating and using a custom login page, starting from a superclass provided by Caché. It is also necessary to create and use a ZAUTHENTICATE routine (starting from the same sample that is provided for use with OAuth 2.0), but your client code does not need to call %session.Login().

The following subsections give the details. A final subsection discusses the ZAUTHENTICATE sample.

Creating and Using a ZAUTHENTICATE Routine for an OAuth 2.0 Client

To create and use a ZAUTHENTICATE routine for a Caché web client that is used as an OAuth 2.0 client, do all of the following:

Creating and Using a Custom Login Page for an OAuth 2.0 Client

To create and use a custom login page for a Caché web client that is used as an OAuth 2.0 client, do all of the following:

  • Create a subclass of %OAuth2.LoginOpens in a new tab. In your subclass:

    • Specify the application name, scope list, and (optionally) response mode. You can specify these items in either or both of the following ways:

      • By specifying parameters of your subclass of %OAuth2.LoginOpens in a new tab.

      • By overriding the DefineParameters() class method. In contrast to specifying parameters, this technique enables you to set these values at runtime.

      The parameters are as follows:

      • APPLICATION — This must be the application name for the application being logged into.

      • SCOPE — This specifies the scope list to be used for the access token request. This must be a blank-separated list of strings.

      • RESPONSEMODE — This specifies the mode of the response. The allowed values are "query" (the default), "fragment" or "form_post".

      The DefineParameters() class method has the following signature:

      ClassMethod DefineParameters(Output application As %String, Output scope As %String, Output responseMode As %String)
      

      This method returns the application name, scope list, and response mode as output arguments. The default implementation of this method returns the values of the APPLICATION, SCOPE, and RESPONSEMODE class parameters.

    • In your subclass of %OAuth2.LoginOpens in a new tab, also specify the properties list for the GetAccessTokenAuthorizationCode() call. To do so, override the DefineProperties() class method. This method has the following signature:

      ClassMethod DefineProperties(Output properties As %String)
      

      This method returns (as output) the properties array, which is a local array specifying additional properties to be included in a token request. The properties array has the following form:

      Node Value
      properties(name), where name is the name of a parameter. Value of the given parameter.

      To add a request parameter that is a JSON object, create a properties element which is an instance of %DynamicObjectOpens in a new tab. Or create a string that is the UTF-8 encoded serialized object.

      To add the request or request_uri request parameters, use the %SYS.OAuth2.RequestOpens in a new tab class to create the JWT. Then, as appropriate, set properties("request") equal to the JWT or set properties("request_uri") equal to the URL of the JWT.

  • Configure the relevant web application to use the custom login page.

  • Create and use a ZAUTHENTICATE routine as described in the previous section, except for the first bullet item. (In this scenario, there is no need for the client code to call %session.Login().)

Notes about the OAUTH2.ZAUTHENTICATE.mac Sample

The OAUTH2.ZAUTHENTICATE.mac sample supports both scenarios described in the previous subsections. In this sample, the GetCredentials() subroutine looks like this:

GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public {
    If ServiceName="%Service_CSP" {
        // Supply user name and password for authentication via a subclass of %OAuth2.Login
        Set Username="OAuth2"
        Set Password=$c(1,2,3)
    }
    Quit $$$OK
}

This subroutine is called if no username and password are provided (which is the case when the custom login page is being used). For the service %Service_CSP, this sample sets the username and password to specific values that are also used in the ZAUTHENTICATE() subroutine (which is called in later processing).

The ZAUTHENTICATE() subroutine includes the following:

If Username="OAuth2",Password=$c(1,2,3) {
    // Authentication is via a subclass of %OAuth2.Login that sets the query parameter CSPOAUTH2
    // with a hash value that allows GetCurrentApplication to determine the application -- 
    // username/password is supplied by GetCredentials.
    Set sc=##class(OAuth2.Response).GetCurrentApplication(.applicationName)
    Set sessionId=%session.SessionId
} Else {
    // If authentication is based on %session.Login, then application and session id are passed in.
    Set applicationName=Username
    Set sessionId=Password
}

A later step calls the isAuthorized() method like this:

Set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(applicationName,sessionId,,.accessToken,,,.error)

If isAuthorized() returns 1, then later code calls the introspection endpoint and uses the information obtained there to define a user:

Set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection(applicationName,accessToken,.jsonObject)
...
Set Username="OAuth2"_jsonObject.sub
Set Properties("FullName")="OAuth account "_Username
Set Properties("Username")=Username
Set Properties("Password")=""    // we don't really care about oauth2 account password
// Set the roles and other Properties as appropriate.
Set Properties("Roles")=roles

Your code could use different logic to obtain the information needed to define the user. You could instead obtain this information in the following ways:

  • From the JWT if the access token is a JWT. In this case, call the ValidateJWT() method of %SYS.OAuth2.Validate.

  • From IDToken if you are using OpenID Connect. In this case, call the ValidateIDToken() of %SYS.OAuth2.Validate.

  • From Userinfo endpoint if you are OpenID Connect. In this case, call the GetUserinfo() of %SYS.OAuth2.AccessTokenOpens in a new tab.

In any case, it is necessary to define a user whose username does not match a normal Caché username.

Your routine would also need to set roles and other parts of the Properties array as needed for your application. See “Creating Delegated (User-Defined) Authentication Code” in the chapter “Using Delegated Authentication” in Caché Security Administration Guide.

Variations

This chapter primarily discusses the scenario in which a Caché web application uses the authorization code grant type. This section discusses some variations:

In the basic scenario described earlier in this chapter, the client receives an access token from the authorization server and then optionally calls additional endpoints in the authorization server: the introspection endpoint, the Userinfo endpoint, or both. After that, the client calls the resource server. Notice that it is also possible for the resource server to independently call these endpoints.

Variation: Implicit Grant Type

In this variation, the client uses the implicit grant type when requesting tokens.

Configuration requirements: See the instructions in “Configuring a Client,” but specify Client Type as appropriate for your use case.

Code requirements: The overall flow is similar to the one for the authorization code grant type, but do not call GetAuthorizationCodeEndpoint(). Instead call the GetImplicitEndpoint() method of the %SYS.OAuth2.AuthorizationOpens in a new tab class:

ClassMethod GetImplicitEndpoint(applicationName As %String, 
                                scope As %String, 
                                redirectURL As %String, 
                                idtokenOnly As %Boolean = 0, 
                                responseMode As %String, 
                                ByRef properties As %String, 
                                Output isAuthorized As %Boolean, 
                                Output sc As %Status
                                sessionId as %String="") As %String

The arguments are as follows:

  • applicationName is the name of the client application.

  • scope is a space-delimited list of scopes for which access is requested, for example: "openid profile scope3 scope4"

    The default is determined by the client configuration for the given applicationName.

  • redirectURL is the URL of the page to which the authorization server should redirect the browser after returning the access token to the client server.

  • idtokenOnly enables you to obtain only an ID token. If this argument is 0, the method obtains both an access token and (if the request includes the appropriate scope) an ID token. If this argument is 1, the method does not obtain an access token.

  • responseMode specifies the mode of the response from the authorization server. This can be "query" (the default), "fragment" or "form_post".

  • properties, which is passed by reference, is a multidimensional array that contains any parameters to be added to the request. This array must have the following structure:

    Array node Array value
    properties(parametername) where parametername is the name of a parameter Value of the given parameter. The value can be a scalar value, an instance of a dynamic object, or the UTF-8 encoded serialized form of a dynamic object.

    Use a dynamic object if you want the request to include a parameter whose value is a JSON object; a scenario is the claims parameter that is defined by OpenID Connect. For details on dynamic objects, see Using JSON in Caché.

    To use the request or request_uri parameter, see the section “Passing Request Objects as JWTs.”

  • isAuthorized, which is returned as output, equals 1 if there is a locally stored access token for this client and this session, and if that access token authorizes all the scopes given by the scope argument. This parameter equals 0 otherwise.

  • sc, which is returned as output, contains the status code set by this method.

  • sessionId specifies the session ID. Specify this only if you want to override the default session (%session.SessionId).

Also see “Variation: Performing the Redirect within OnPreHTTP.”

Variation: Password Credentials Grant Type

In this variation, the client uses the password credentials grant type when requesting tokens. You can use this grant type when the client has the password belonging to the resource owner. The client application can simply perform an HTTP POST operation to the token endpoint, without any page redirection; Caché provides a method to do this.

Configuration requirements: See the instructions in “Configuring a Client,” but note that you do not need to specify Client Secret. (In general, you should use the client secret only when the client secret is needed and it is possible to protect the client secret.)

Code requirements: Your application should do the following:

  1. Call the IsAuthorized() method of %SYS.OAuth2.AccessTokenOpens in a new tab and check the returned value (and possible error), as described in “Obtaining Tokens,” earlier in this chapter.

  2. If IsAuthorized() returned 0, call the GetAccessTokenPassword() method of %SYS.OAuth2.AuthorizationOpens in a new tab.

    ClassMethod GetAccessTokenPassword(applicationName As %String, 
                                       username As %String, 
                                       password As %String, 
                                       scope As %String, 
                                       ByRef properties As %String,
                                       Output error As %OAuth2.Error) As %Status
    

    The arguments are as follows:

    • applicationName is the name of the client application.

    • username is a username.

    • password is the corresponding password.

    • scope is a space-delimited list of scopes for which access is requested, for example: "scope1 scope2 scope3"

      The default is determined by the client configuration for the given applicationName.

    • properties, which is passed by reference, is a multidimensional array that contains any parameters to be added to the request. This array must have the following structure:

      Array node Array value
      properties(parametername) where parametername is the name of a parameter Value of the given parameter. The value can be a scalar value, an instance of a dynamic object, or the UTF-8 encoded serialized form of a dynamic object.

      Use a dynamic object if you want the request to include a parameter whose value is a JSON object; a scenario is the claims parameter that is defined by OpenID Connect. For details on dynamic objects, see Using JSON in Caché.

    • error, which is returned as output, is either null or is an instance of OAuth2.Error that contains error information.

    This method performs an HTTP POST operation to the token endpoint, and then receives and saves the access token (if any).

  3. Check the error argument and proceed accordingly.

  4. Continue as described in “Examining the Token(s)” and “Adding an Access Token to an HTTP Request.”

Variation: Client Credentials Grant Type

In this variation, the client uses the client credentials grant type when requesting tokens. This grant type enables the client application to communicate with the resource server independently from any user. There is no user context. The client application can simply perform an HTTP POST operation to the token endpoint, without any page redirection; Caché provides a method to do this.

Configuration requirements: See the instructions in “Configuring a Client.” Make sure to specify the Client Type as Private and specify Client Secret.

Code requirements: Your application should do the following:

  1. Call the IsAuthorized() method of %SYS.OAuth2.AccessTokenOpens in a new tab and check the returned value (and possible error), as described in “Obtaining Tokens,” earlier in this chapter.

  2. If IsAuthorized() returned 0, call the GetAccessTokenClient() method of %SYS.OAuth2.AuthorizationOpens in a new tab.

    ClassMethod GetAccessTokenClient(applicationName As %String, 
                                     scope As %String, 
                                     ByRef properties As %String, 
                                     Output error As %OAuth2.Error) As %Status
    

    The arguments are as follows:

    • applicationName is the name of the client application.

    • scope is a space-delimited list of scopes for which access is requested, for example: "scope1 scope2 scope3"

      The default is determined by the client configuration for the given applicationName.

    • properties, which is passed by reference, is a multidimensional array that contains any parameters to be added to the request. See the properties argument for GetAccessTokenPassword(), in the previous subsection.

    • error, which is returned as output, is either null or is an instance of OAuth2.Error that contains error information.

    This method performs an HTTP POST operation to the token endpoint, and then receives and saves the access token (if any).

  3. Check the error argument and proceed accordingly.

  4. Continue as described in “Examining the Token(s)” and “Adding an Access Token to an HTTP Request.”

Variation: Performing the Redirect within OnPreHTTP

For the authorization code and implicit grant types, the previous instructions use the following steps:

  1. Call the IsAuthorized() method of %SYS.OAuth2.AccessTokenOpens in a new tab.

  2. Call the GetAuthorizationCodeEndpoint() method (for the authorization code grant type) or call the GetImplicitEndpoint() method (for the implicit grant type).

  3. Provide an option (such as a button) that opens the URL returned by the previous step, thus enabling the user to authorize the request

An alternative is to modify the OnPreHttp() method of the page class (in your application), so that it calls either the GetAccessTokenAuthorizationCode() method (for the authorization code grant type) or call the GetAccessTokenImplicit() method (for the implicit grant type). These methods cause the browser to navigate directly (if needed) to the authentication form of the authorization server, without first displaying any content of your page.

Variation: Passing Request Objects as JWTs

Caché also supports passing the request object as a JWT, as specified in section 6Opens in a new tab of the OpenID Connect Core specification. You can pass the request object by value or by reference.

In both cases, you use methods of the %SYS.OAuth2.RequestOpens in a new tab class. See the class reference for additional methods not described in this section.

Passing a Request Object by Value

To use the request parameter to pass the request object as a JWT:

  1. Call the MakeRequestJWT() method of the %SYS.OAuth2.RequestOpens in a new tab class:

    ClassMethod MakeRequestJWT(applicationName As %String, 
                               ByRef properties As %String, 
                               Output sc As %Status) As %String
    

    Where:

    • applicationName is the name of the client application.

    • properties, which is passed by reference, is a multidimensional array that contains any parameters to be added to the request. This array must have the following structure:

      Array node Array value
      properties(parametername) where parametername is the name of a parameter Value of the given parameter. The value can be a scalar value, an instance of a dynamic object, or the UTF-8 encoded serialized form of a dynamic object.
    • sc, which is returned as output, contains the status code set by this method.

    This method returns a string, which is the JWT. For example:

     // create jwt 
     set jwt=##class(%SYS.OAuth2.Request).MakeRequestJWT("myapp",.properties,.sc)
    
  2. Modify the properties array that you will use as the argument for GetAuthorizationCodeEndpoint() or GetImplicitEndpoint(). Set the node properties("request") equal to the JWT that you created in the previous step. For example:

     set properties("request")=jwt
  3. When you call GetAuthorizationCodeEndpoint() or GetImplicitEndpoint(), include the properties array. For example:

     set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint("myapp",
           scope,redirect,.properties,.isAuthorized,.sc, responseMode)
    

Passing a Request Object by Reference

To use the request_uri parameter to pass the request object as a JWT:

  1. Call the UpdateRequestObject() method of the %SYS.OAuth2.RequestOpens in a new tab class:

    ClassMethod UpdateRequestObject(applicationName As %String, 
                                    requestName As %String, 
                                    ByRef properties As %String, 
                                    Output sc As %Status) As %SYS.OAuth2.Request
    

    Where:

    • applicationName is the name of the client application.

    • requestName is the name of the request.

    • properties, which is passed by reference, is a multidimensional array that contains any parameters to be added to the request. This array must have the following structure:

      Array node Array value
      properties(parametername) where parametername is the name of a parameter Value of the given parameter. The value can be a scalar value, an instance of a dynamic object, or the UTF-8 encoded serialized form of a dynamic object.
    • sc, which is returned as output, contains the status code set by this method.

    This method creates, saves, and returns an instance of %SYS.OAuth2.RequestOpens in a new tab.

     // create requestobject 
     set requestobject=##class(%SYS.OAuth2.Request).UpdateRequestObject("myapp","myrequest",.properties,.sc)
    
  2. Get the URL of the saved request object. To do so, call the GetURL() method of the instance. Note that GetURL() returns a status code as output in the first argument; your code should check that.

     Set requesturl=requestobject.GetURL()
  3. Modify the properties array that you will use as the argument for GetAuthorizationCodeEndpoint() or GetImplicitEndpoint(). Set the node properties("request_uri") equal to the URL obtained in the previous step. For example:

    set properties("request_uri")=requesturl
    
  4. When you call GetAuthorizationCodeEndpoint() or GetImplicitEndpoint(), include the properties array. For example:

     set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint("myapp",
           scope,redirect,.properties,.isAuthorized,.sc, responseMode)
    
    

Variation: Calling Other Endpoints of the Authorization Server

The methods in %SYS.OAuth2.AuthorizationOpens in a new tab enable you to call a specific set of endpoints in the authorization server. If the authorization server has other endpoints, use the following general process to call them:

  1. Create an instance of %Net.HttpRequestOpens in a new tab, set its properties as needed, and call methods as needed, in order to define the request.

    Set httpRequest=##class(%Net.HttpRequest).%New()
    Set httpRequest.ContentType="application/x-www-form-urlencoded"
    ...
    

    For details on this class, see “Sending HTTP Requests” in Using Caché Internet Utilities.

  2. To add authentication to the request, call the AddAuthentication() method of %SYS.OAuth2.AccessTokenOpens in a new tab.

    ClassMethod AddAuthentication(applicationName As %String, httpRequest As %Net.HttpRequest) As %Status
    

    Where:

    Caché looks up the given client and uses its Authentication type, SSL configuration, and other information to add the appropriate authentication to the request.

  3. Optionally open the client configuration so that you can use properties contained in it. To do so, switch to the %SYS namespace and call the Open() method of OAuth2.ClientOpens in a new tab, passing the client name as the argument:

     New $NAMESPACE
     set $NAMESPACE="%SYS"
     Set client=##class(OAuth2.Client).Open(applicationName,.sc)
     If client="" Quit
  4. Call the Post(), Get(), or Put() method (as appropriate) of the HTTP request object, providing the authorization server’s token endpoint as the argument. For example:

     set sc=httpRequest.Post(client.ServerDefinition.TokenEndpoint)
  5. Perform additional processing as needed.

Revoking Access Tokens

If the authorization server supports token revocation, you can revoke access tokens via the Management Portal or programmatically.

Revoking a User’s Access Tokens

To revoke all the access tokens for a given user, do the following:

  1. Select System Administration > Security > OAuth 2.0 > Administration.

  2. Type the user ID into the field Revoke tokens for user.

  3. Select Revoke.

To perform this task, you must be logged in as a user who has USE permission on the %Admin_Secure resource.

Revoking Access Tokens Programmatically

If it is necessary for the client to revoke an access token, use the RevokeToken() method of %SYS.OAuth2.AccessTokenOpens in a new tab. Note that when the session holding a given token is deleted, the system automatically calls this method (if a revocation endpoint is specified).

ClassMethod RevokeToken(applicationName As %String, accessToken As %String) As %Status

The arguments are as follows:

  • applicationName is the name of the client application.

  • accessToken is the access token.

The request is authorized using the basic authorization HTTP header with the client_id and client_secret associated with applicationName.

For example:

set sc=##class(%SYS.OAuth2.AccessToken).RevokeToken("myclient",accessToken)
if $$$ISERR(sc) {
    //error handling here
}

Note that you cannot use this method if the server does not specify a revocation endpoint or if Client secret is not specified.

%SYS.OAuth2.AccessTokenOpens in a new tab also provides the method RemoveAccessToken(), which removes the access token from the client but does not remove the token from the server.

Rotating Keys Used for JWTs

In most cases, you can cause the client 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 client, the client uses the new private RSA keys to sign JWTs to be sent to the authorization server. Similarly, the client uses the new public RSA keys to encrypt JWTs to be sent to the authorization server. To decrypt JWTs received from the authorization server, the client uses the new RSA keys, and if that fails, uses the old RSA keys; thus the client can decrypt a JWT that was created using its old public RSA keys. Last, if the client cannot verify a signed JWT received from the authorization server, then if the client has the URL for the authorization server public JWKS, the client obtains a new public JWKS and tries again to verify the signature. (Note that the client has a URL for the authorization server public JWKS if the client was registered dynamically or if the configuration specified the JWKS from URL option; otherwise, the client does not have this URL.)

To rotate keys for a given client configuration:

  1. In the Management Portal, select System Administration > Security > OAuth 2.0 > Client Configuration.

  2. Select the server description with which the client configuration is associated.

    The system then displays all client configurations associated with that server description.

  3. Select the configuration of the client whose keys you want to rotate.

  4. 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 Client

To rotate keys programmatically on the client, call the RotateKeys() method of OAuth2.ClientOpens in a new tab.

To obtain a new authorization server public JWKS, call the UpdateJWKS() method of OAuth2.ServerDefinitionOpens in a new tab.

For details on these methods, see the class reference.

Getting a New Public JWKS from the Authorization Server

In most cases, the authorization server generates a public/private pair of JWKSs. There are different ways in which the client can receive the public JWKS. One way is for the authorization server to provide the public JWKS at a URL; see the JWKS from URL option in “Manually Creating a Server Description (No Discovery).”

If the authorization server was defined with JWKS from URL and if the authorization server generates a new pair of JWKSs, you can cause the client 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 > Client Configuration.

  2. Select the server description with which the client configuration is associated.

    The system then displays all client configurations associated with that server description.

  3. Select the configuration of the client.

  4. Select the Update JWKS button.

If the authorization server was not defined with JWKS from URL and if the authorization server generates a new pair of JWKSs, it is necessary to obtain the public JWKS, send it to the client, and load it from a file.

FeedbackOpens in a new tab