A custom scope handler

If you wish to add support to an OA4MP OIDC server, you may do this by specifying scopes that are supported in the server configuration file. The client must request these. If you do not specify the additional scopes in the server configuration, then requests for these from a client will cause the server will reject the request. This blurb is about implementing your own handler.

Remember that if you specify a custom scope handler, it will always be called and replaces the BasicScopeHandler which is the default. Even if you do not have a custom scope, this will let you completely rewrite all claims.

The lifecycle of the handler is simple. It is created at server startup and is called whenever needed. This may either be a full implementation of the ScopeHandler interface or, more easily, an extension of the BasicScopeHandler class. In both cases you implement or override the process methods. There are two of these:

  public UserInfo process(UserInfo userInfo, ServiceTransaction transaction) throws UnsupportedScopeException;
  public UserInfo process(UserInfo userInfo, HttpServletRequest request, ServiceTransaction transaction) throws UnsupportedScopeException;

This receives a UserInfo object which has been populated by the server with the default claims. The service transaction has all of the information that the system has on the current user. Simply set whatever values you want returned in the UserInfo object and return that. If you choose the method with the servlet request, then you will be passed the current request, which includes the headers and other information.

Note especially that the UserInfo object has many, many convenience mutators. If you have some specific claims you need to return, simply set them using the put methods or if you need something more exotic, get the underlying JSON object with the getMap call and set the key/value pair directly. The response to the client will take the underlying JSON object and serialize it.

Loading your custom handler.

The easiest way to do this is to extends the environment and simply specify the handler. This consists of 4 steps as follows

  1. Either implement the ScopeHandler interface, or extend the BasicScopeHandler
            public class MyScopeHandler extends BasicScopeHandler{
    
            @Override
               public UserInfo process(UserInfo userInfo, ServiceTransaction transaction) throws UnsupportedScopeException {
                // Set whatever you need in the userInfo object then return it
                return userInfo;
               }
            @Override
                public UserInfo process(UserInfo userInfo, HttpServletRequest request, ServiceTransaction transaction) throws UnsupportedScopeException {
                 // Set whatever you need in the userInfo object then return it
                 return userInfo;
                }
            }
        
    This example is functionally equivalent to the BasicScopeHandler. The ServiceTransaction passed in is, in point of fact, an instance of OA2ServiceTransaction, so you can see what information is available to the method. Note that not every property in the service transaction will be available. This also means that the current user's name is available in the service transaction. For instance, the user might not have gotten a protected assest (i.e. a certificate) before the call to the userinfo endpoint was made. The transaction also has a getScopes() call. This returns the scopes requested by the user for this specific transaction.
  2. Extend OA2ConfigurationLoader and override getScopeHandler, e.g.
            public class MyConfigurationLoader<T extends OA2SE> extends OA2ConfigurationLoader<T>{
                public MyLoader(ConfigurationNode node){
                     super.node();
                }
                public myLoader(ConfigurationNode node, MyLoggingFacade logger) {
                     super(node, logger);
                }
    
            @Override
                public ScopeHandler getScopeHandler() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
                     scopeHandler = new MyScopeHandler();
                     scopeHandler.setScopes(getScopes()); // this is a complete list of scopes from the configuration file, if needed.
                }
            }
        
  3. Extend the OA42Bootstrapper to point to this, e.g.:
            public class MyBootstrapper extends OA2Bootstrapper{
            @Override
              public ConfigurationLoader getConfigurationLoader(ConfigurationNode node) throws MyConfigurationException {
                  return new MyLoader(node);
              }
            }
        
  4. In your web.xml file, Point Tomcat at your bootstrapper:
            <listener>
               <listener-class>path.to.MyBootstrapper</listener-class>
            </listener>
        

When the server boots, it should find everything and your handler should be used.