Runtime behavior implementation
After you specify your plugin’s API at least partially, you can start implementing the runtime behavior. Use the specification that you defined previously to implement the runtime functionality.
Follow this pattern in lookupAuthN()
:
-
Check for the possible actions the adapter expects in the current state.
-
If an action is matched, then try to extract the expected model from the request and handle the action.
-
If an action is requested, but it does not match an action allowed for the current state, then return an
INVALID_ACTION_ID
error. -
If no action is requested, render the response for the current state.
The AuthnApiSupport
class provides much of the functionality for handling API requests and sending responses. The TemplateRenderAdapter
stores a reference to this singleton in its apiSupport
field.
private AuthnApiSupport apiSupport = AuthnApiSupport.getDefault();
Checking for actions
The following code shows the preferred approach for checking for the submitIdentifiers
action.
The adapter performs this check two ways, depending on whether the current request is from the API endpoint. The TemplateRenderAdapter
uses a slightly different but equivalent method.
/** * Determine if the user chose "Submit". */ private boolean isSubmitAttributesRequest(HttpServletRequest req) { if (apiSupport.isApiRequest(req)) { return ActionSpec.SUBMIT_USER_ATTRIBUTES.isRequested(req); } return StringUtils.isNotBlank(req.getParameter("pf.submit")); }
Extracting models from requests
The next step extracts the model from the request. This step varies depending on whether the request is from the API endpoint. For an API request, call the AuthnApiSupport.deserializeAsModel()
method. For a non-API request, you must build the model from the parameters in the request.
private SubmitUserAttributes getSubmittedAttributes(HttpServletRequest req) throws AuthnErrorException, AuthnAdapterException { if (apiSupport.isApiRequest(req)) { try { return apiSupport.deserializeAsModel(req, SubmitUserAttributes.class); } catch (IOException e) { throw new AuthnAdapterException(e); } } else { SubmitUserAttributes result = new SubmitUserAttributes(); result.setUsername(req.getParameter("username")); for (String key : extendedAttr) { result.getUserAttributes().put(key, req.getParameter(key)); } return result; } }
The deserializeAsModel()
method also does some validation on the incoming JSON. This includes checking for fields flagged as required
in the model, using the @Schema annotation. If a validation error occurs during this step, the method throws an AuthnErrorException
, which the adapter can convert to an API error response. For more information, see Handling authentication error exceptions.
Performing additional validation
The deserializeAsModel()
method performs some basic validation on the submitted JSON. Your adapter probably needs to perform more validation and send an AuthnError
to the API client if it finds any errors. Here is how the TemplateRenderAdapter
validates the names of the provided user attributes:
private void validateSubmittedAttributes(HttpServletRequest req, SubmitUserAttributes submitted) throws AuthnErrorException { if (apiSupport.isApiRequest(req)) { List<AuthnErrorDetail> errorDetails = new ArrayList<>(); for (String attrName : submitted.getUserAttributes().keySet()) { if (!extendedAttr.contains(attrName)) { errorDetails.add(ErrorDetailSpec.INVALID_ATTRIBUTE_NAME.makeInstanceBuilder() .message("Invalid attribute name: " + attrName).build()); } } if (!errorDetails.isEmpty()) { AuthnError authnError = CommonErrorSpec.VALIDATION_ERROR.makeInstance(); authnError.setDetails(errorDetails); throw new AuthnErrorException(authnError); } } }
Handling invalid action IDs
If a request from an API client includes an action ID that does not match any actions available in the current state, it is best practice to return an error to the client.
After checking all the possible actions, if none match and the request’s action ID is not null, the adapter can throw an AuthnErrorException
. The adapter catches this exception and writes an error to the API response.
if (apiSupport.getActionId(req) != null) { // An action ID was provided but it does not match one of those expected in the current state. throw new AuthnErrorException(CommonErrorSpec.INVALID_ACTION_ID.makeInstance()); }
Handling authentication error exceptions
If the deserializeAsModel()
method detects an error while deserializing the model, it throws an AuthnErrorException
. If the added validation checks in validateSubmittedAttributes
detect an error, they also throw this exception.
The adapter should catch this exception and send an API error response using the method AuthnApiSupport.writeErrorResponse()
.
try { ... } catch (AuthnErrorException e) { // A validation error occurred while processing an API request, return an error response to the API client apiSupport.writeErrorResponse(req, resp, e.getValidationError()); authnAdapterResponse.setAuthnStatus(AUTHN_STATUS.IN_PROGRESS); return authnAdapterResponse; }
Sending API responses
AuthnApiSupport
provides several methods for writing API responses.
The following example shows how the TemplateRenderAdapter
writes the response for the USER_ATTRIBUTES_REQUIRED
state.
private void renderApiResponse(HttpServletRequest req, HttpServletResponse resp, Map<String, Object> inParameters) throws AuthnAdapterException { UserAttributesRequired model = new UserAttributesRequired(); model.setAttributeNames(new ArrayList<>(extendedAttr)); AuthnState<UserAttributesRequired> authnState = apiSupport.makeAuthnState(req, StateSpec.USER_ATTRIBUTES_REQUIRED, model); try { apiSupport.writeAuthnStateResponse(req, resp, authnState); } catch (IOException e) { throw new AuthnAdapterException(e); } }
The makeAuthnState()
method takes an AuthnStateSpec
and an instance of the model for that state and builds an AuthnState
object. The AuthnState
object can then be further modified. For example, you could remove an action that is not currently applicable using the removeAction()
method. Then you write the AuthnState
object to the response using the writeAuthnStateResponse()
method.