Skip to content

Authentication and Authorization

Pascal Knüppel edited this page Mar 22, 2021 · 6 revisions

Enabling Authentication and Authorization

The Authentication and Authorization will be activated as soon as you add an implementation of the Authorization implementation to the resourceEndpoint.handleRequest(...) method. Be careful to never add a null-value since this would deactivate the authentication again and the request will be executed unauthenticated.

The following example utilizes the authentication and authorization feature:

ScimResponse scimResponse = resourceEndpoint.handleRequest(request.getRequestURL().toString() + query,
                                                           HttpMethod.valueOf(request.getMethod()),
                                                           requestBody,
                                                           httpHeaders, 
                                                           new MyCustomAuthorizationImpl(httpHeaders));

And the following example does not perform any authentication. The endpoints can be executed without authorization:

ScimResponse scimResponse = resourceEndpoint.handleRequest(request.getRequestURL().toString() + query,
                                                           HttpMethod.valueOf(request.getMethod()),
                                                           requestBody,
                                                           httpHeaders);

The reason for this is the following method of class ResourceEndpoint:

  private void authenticateClient(UriInfos uriInfos, Authorization authorization)
  {
    ResourceType resourceType = uriInfos.getResourceType();
    if (!resourceType.getFeatures().getAuthorization().isAuthenticated())
    {
      // no authentication required for this endpoint
      return;
    }
    if (authorization == null)
    {
      log.warn("Endpoint '{}' requires authentication but received no '{}' implementation. Authentication "
               + "and Authorization will be bypassed.",
               uriInfos.getResourceEndpoint(),
               Authorization.class.getName());
    }
    else
    {
      boolean isAuthenticated = authorization.authenticate(uriInfos.getHttpHeaders(), uriInfos.getQueryParameters());
      if (!isAuthenticated)
      {
        log.error("authentication has failed");
        throw new UnauthenticatedException("not authenticated", getServiceProvider().getAuthenticationSchemes(),
                                           authorization.getRealm());
      }
    }
  }

This was an intentional decision and creates only bypassed authentication if a null-instance of the Authorization-interface was received. So make sure to always pass a valid implementation.

@available since 1.2.0

Authorization

This page will explain how authorization is hanlded on endpoints for specific resource types.

First of all you can set authorization roles directly within the resource type definition. This is useful if you know the required roles for accessing the endpoints in advance. Alternatively you may set the necessary roles within the ResourceType instances.

Example

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
  ],
  "id": "User",
  "name": "User",
  "description": "User Account",
  "schema": "urn:ietf:params:scim:schemas:core:2.0:User",
  "endpoint": "/Users",
  "schemaExtensions": [
    {
      "schema": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
      "required": false
    }
  ],
  "urn:gold:params:scim:schemas:extension:url:2.0:ResourceTypeFeatures": {
    "authorization": {
      "roles": [
        "admin"
      ],
      "rolesCreate": [
        "create"
      ],
      "rolesGet": [
        "get"
      ],
      "rolesUpdate": [
        "update"
      ],
      "rolesDelete": [
        "delete"
      ]
    }
  }
}

What exactly does this example tell us?

  • roles: required roles for all endpoints on the resource type.
  • rolesCreate: required roles for the create endpoint. This attribute overrides the attribute roles for the create endpoint. The roles set in the roles attribute are completely ignored.
  • rolesGet: required roles for the get and list endpoint. This attribute overrides the attribute roles for the get and list endpoint. The roles set in the roles attribute are completely ignored.
  • rolesUpdate: required roles for the update and patch endpoint. This attribute overrides the attribute roles for the update and patch endpoint. The roles set in the roles attribute are completely ignored.
  • rolesDelete: required roles for the delete endpoint. This attribute overrides the attribute roles for the delete endpoint. The roles set in the roles attribute are completely ignored.

Authentication

@available since 1.7.0

Configure authentication

additional support for authentication behaviour was added. In some cases you might want to handle access to specific resources only if the client or user is authenticated. To other resources though e.g. the service provider configuration, the schemas endpoint or the resource types endpoint the authentication should not be required and readable for everyone. So it is possible to add authentication requirement to a specific resource type. The authentication is set to true by default except for the three endpoints "/ServiceProviderConfig", "/ResourceTypes" and "/Schemas". So to deactivate the authentication requirement you can disable it in the resource type configuration. Either by writing it into the json document or by code:

Example

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
  ],
  "id": "User",
  "name": "User",
  "description": "User Account",
  "schema": "urn:ietf:params:scim:schemas:core:2.0:User",
  "endpoint": "/Users",
  "schemaExtensions": [
    {
      "schema": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
      "required": false
    }
  ],
  "urn:gold:params:scim:schemas:extension:url:2.0:ResourceTypeFeatures": {
    "authorization": {
      "authenticated": false
    }
  }
}

if the property is not set true is expected to be the correct value.

alternatively you can do something like this to do this in code:

    ResourceType userResourceType = resourceEndpoint.registerEndpoint(new UserEndpointDefinition(new UserHandler()));
    userResourceType.getFeatures().getAuthorization().setAuthenticated(false);

Thats the way how you can disable authentication on specific resource types. And now we get to how to do the authentication:

Authenticate the client/user

Use the de.captaingoldfish.scim.sdk.server.endpoints.authorize.Authorization interface. It has been extended by two default methods (default methods to preserve backwards compatability):

  /**
   * this method can be used to authenticate a user. This method is called on a request-base which means that
   * the authentication method is executed once for each request that requires authentication
   * 
   * @param httpHeaders in case that the authentication details are sent in the http headers
   * @param queryParams in case that authentication identifier are used in the query
   * @return true if the user / client was successfully be authenticated, false else
   * @see <a href=
   *      "https://github.com/Captain-P-Goldfish/SCIM-SDK/wiki/Authentication-and-Authorization#authentication">
   *      https://github.com/Captain-P-Goldfish/SCIM-SDK/wiki/Authentication-and-Authorization#authentication
   *      </a>
   */
  default boolean authenticate(Map<String, String> httpHeaders, Map<String, String> queryParams)
  {
    return true;
  }

  /**
   * the current realm for which the authentication should be executed. This value will be present in the
   * WWW-Authenticate response header of the {@link de.captaingoldfish.scim.sdk.common.response.ErrorResponse}
   * object if the authentication has failed
   */
  default String getRealm()
  {
    return "SCIM";
  }

the default implementation will always expect the user to be authenticated so you have to override the method and do your authentication implementation. If the json property "authenticated" in the resource type definition is set to false the "authenticate" method will not be called for this resource type!

The "getRealm" method is optional and is simply a value that will be put into the HTTP response headers of a ScimResponse that you can manually add to the response. If the user did not authenticate correctly or missed to send authentication details a WWW-Authenticate header is added with the realm that was returned by the "getRealm" method.

How authentication is being handled for Bulk requests

If a bulk request with several operations is being executed the authenticate method is called for each of those operations. This is done because it is possible to deactivate authentication on specific resource types. So a bulk request might access several endpoints with actions that require authentication and some that do not require authentication. The resource type information of the operation that should be executed is gathered just before its execution so it was not easily possible to do a one time authentication for bulk requests. So in order to prevent having the authentication executed several times in a row for bulk requests you should implement something like this:

  /**
   * authenticates the user
   */
  @Override
  public boolean authenticate(Map<String, String> httpHeaders, Map<String, String> queryParams)
  {
    if (authResult == null)
    {

      try
      {
        authResult = Authentication.authenticate(keycloakSession);
        return true;
      }
      catch (NotAuthorizedException ex)
      {
        log.error("authentication failed", ex);
        return false;
      }
    }
    else
    {
      return true;
    }
  }