OpenAPI Security

Introduction

The Open API Suite Security implements the requirements regarding security in the Open API 2.0 specification. The OpenAPI Suite has a design supporting the three required security schemes using OpenAPI Authenticator services and more.

Although called security in the OpenAPI specification, this security is actually limited to authentication. This seems to imply that the answer from an OpenAPI Authenticator service is a simple boolean. However, in the OpenAPI specifications, it is possible to require multiple authenticators in any combination of and and or. For example, it is (theorethically) possible to require a Basic Authentication or and OAuth authentication. It also frequently falls short of providing critical information. (For example, OAuth2 is not an authentication protocol.) Though this is extremely flexible the specification does lack the details of how this could be implemented. The OpenAPI Suite provides a full implementation by providing pragmatic implementations.

The set of OpenAPI 2.0 specifications security schemes is rather limited. However, the OpenAPI Suite allows the existing schemes to be used with other authentication schemes. For example, it is straighforward to implement rate limiting schemes, monitoring, or support more secure forms of Basic Authentication.

The OpenAPI specifications are moot on the authorization aspects of writing microservices. However, authentication is closely connected to authorization. The identity that is established with authentication is used to link to the set of permissions that the request has. Unfortunately, in Java this is an area with lots of choices and even a number of false starts. For this reason, it handles all security decisions through a lightweight service. It provides an implementation based on Apache Shiro and OSGi User Admin for out of the box operations.

Essentials

Entities

Architecture

The OpenAPI security architecture provides a pluggable system for the myriad of authentication and authorisation systems out there. It does this with a number of services. The service diagram is depicted in the following picture:

OpenAPI Security Diagram

Security Definitions

The OpenAPI Specification has a securityDefinitions section in the prolog. In this section, different authenticators that are later used for the operations can be defined. Each definition will get a name, a type, and the parameters for the defined type.

In the OpenAPI suite the names used for the security definitions are globals during runtime. They are mapped to services properties of the OpenAPI Authenticator.

An OpenAPI security section definining a basic authenticator called basicauth looks like:

           "swagger": "2.0",
           "securityDefinitions": {
                "basicauth": {
                    "type": "basic"
                }
           }

Basic Authentication has no parameters.

During runtime, this will refer to a service with the following properties:

objectClass     OpenAPIAuthenticator
openapi.name            basicauth
openapi.type            basic

There should only be one OpenAPIAuthenticator service with these properties registered.

OpenAPI Specification Security Section

The security section can be defined in the prolog and overridden for each operation. In the prolog it defines the default security that is applied when no security section is defined in an operation. The security section primarily refers to a security definition although in the case of OAuth it can provide additional parameters for the local check. It looks as follows:

           "security": [ {
                     "basicauth":[]
           } ]

The security section is an array of security requirements. Each member of the array is an alternative. That is, any of the security requirements can match to succeed. The security requirement is a map where the keys are the names of the security definitions and the value is an array holding additional parameters. If you think this allows for mindboggling complex authentication schemes then we agree.

Security During Runtime

The code generated from the security information will authenticate the user just before the validation and the actual call to the microservice implementation. The authentication phase will try to find one of the alternative security requirements. However, this is harder then it sounds. Some authentication schemes can request credentials or take some action so that they can be authenticated. However, if one of the security requirements succeeds there should be no need to ask for credentials. The authentication code will therefore try to find a security requirement that succeeds. Not that this is non-trivial since there each security requirement is part of an and/or combination.

If no security requirement succeeds authentication, the first OpenAPI Authenticator that could request credentials is asked to ask the caller to provide credentials. Note that this can result in a number of roundtrips since a security requirement can have siblings that all need to authenticate.

For example, Basic Authentication checks the Authorization header. If there is no such header it can request such a header by setting the WWW-Authenticate header and returning a 403 error code to the caller. The Basic Authentication OpenAPI Authenticator will there indicate that it is not authenticated but it could set the response to request credentials. If the OpenAPI Runtime cannot find another OpenAPI Authenticator that says it is authenticated it will ask the Basic Authentication OpenAPI Authenticator service to request the credentials.

If in the end a security requirement and its and subling succeeds, the first security requirement in the OpenAPI source file is selected to provide the authenticated user.

If no authenticated user can be established, the request is executed with the anonymous user. This is the user represented by null. That is, authentication will never prohibit calling the microservice implementation, microservices should always verify their required permissions.

This sounds (and is) complicated. However, in most of the cases there is only a single security provider in play, which greatly simplifies the setup.

The OpenAPI Security Environment Service

The best asset of Java is the unsurpassed richness of its environment. The worst of Java is the myriad of choices developers have to make, choices that often constraint other choices. There are hundreds of security protocols and for each protocol there are different solutions in Java. Each of these solutions must make a choice about the following questions:

One possible candidate would be to use Apache Shiro. This is an API that is OSGi compatible and provides an elegant abstraction for the aforementioned questions without dragging in too many dependencies. For this reason, Apache Shiro is supported out of the box. However, there are other solutions out there and sometimes very simple solutions will do. For this reason, the OpenAPI Security Environment service uses its own API and provides bridges to other environments. For many applications it is likely that the application will implement this service to bind it to the implementations it requires.

In the following sections the aspects of the OpenAPI Security Environment service are discussed. Consult the Java docs for the finer details.

User Identity

The most straightforward authentication mechanism for the web is Basic Authentication. This protocol adds an Authorization header to the request containing the user id and the password. The OpenAPI suite Basic Authentication Security Provider can parse this header. However, it will need to understand what the user identity actually is in the environment it runs.

It is good practice that the actual identity is not the same as the name used in a login. For example, a common practice is to use the email address of a user to identify the user. However, using that email address as the user identity is generally a decision that will be regretted once the user changes his email address. Best practice is to make the user identity some random number that has no semantics. The email address is then a property of the user. The Basic Authentication OpenAPI Authenticator will therefore find a user identity by looking for a user with the property email=credential.

This indirection is generally the case for OpenAPI Authenticator service implementations. For example, OAuth2 with OpenID generally returns an email address as well.

The following table shows

#id          email          first_name           last_name
1232423      john@doe.com   John                  Doe
6435234      mary@doe.com   Mary                  Doe

The OpenAPI Security Environment service has a method that allows finding a user by a providing a property (key and value) that must be a property of a single user. Clearly, the key of this property (email) needs to be configurable because not all systems have the same conventions. In the OpenAPI suite the default is always email and can be overridden with a convention, including using the user identity directly.

User Properties and Credentials

In the case of Basic Authentication the OpenAPI Authenticator must verify the password. Clearly, storing the password is simple but not a good idea. Most losses of passwords are caused by databases that got stolen. For this reason, a value is stored that can be calculated from the password but there is no function that can calculate the password from it, for example a hash. The Basic Authentication the OpenAPI Authenticator must clearly store this value somewhere. In practice, it can actually require to store multiple values since the safest known method is to store both a random number (the used salt) and the hash.

For this reason, the OpenAPI Security Environment service provides a general store for properties and credentials. Properties are <String,String> and credentials are <String,byte[]>.

The OpenAPI Authenticator implementations must be able to read and write the properties and credentials. Though the need to read should be obvious, the need to write requires some explanation.

The reason for the write is that the values that are used are highly coupled to the OpenAPI Authenticator implementation as well as its configuration. For example, the email key to link the information passed in an Authorization header is a configurable aspect of the Basic Authentication OpenAPI Authenticator service.

However, this provider also has many different algorithms and other settings that define what kind of hash is stored. Although it is always possible to create an application component that has the same settings and knows how to calculate the proper hash, it is better to delegate this work to the actual security provider. For this reason, the OpenAPI Authenticator service implementations register a special service with domain specific methods.

For example, the Basic Authentication OpenAPI Authenticator has a setPassword method. Common practice is to also provide a Gogo command and sometimes a Webconsole plugin.

Authorization

The OpenAPI Authenticator services are used to authenticate a user. The take the credentials from a web request and through some protocol, which can include browser redirects, establish a user identity for the caller. This user identity can include the anonymous user.

The next step is to verify the authorization. Since there are many different ways to authorize code, the OpenAPI suite delegates this task to the OpenAPI Security Environment service. When it has established a current user identity for the request it will call the OpenAPISecurity.call(...) method that receives the user identity and a lambda. The implementation of this service must then establish a security context that remains valid during the execution of the lambda. The lambda will then call the microservice implementation.

This approach allows the application to use an application specific authorization model, the OpenAPI suite does not require the use of a specific authorization model.

Implementations will in general associate the security context with the current thread and provide some service used by the microservice implementations to get this context.

For convenience, the base class OpenAPI Base provides the hasPermission and checkPermission methods that will directly call the Open API Security service, leaving the actual model in one place. However, there is no requirement to use these methods since a microservice implementation can instead call an application specific service for authorization.

@Override
public void foo() {
  hasPermission("foo");
}

The following diagram shows the actions in this design:

OpenAPI Security Context

Why No Annotations To Check Permissions?

It would be simple to verify basic permissions using an annotation on the microservice implementation method. However, annotations have a number of drawbacks over plain old java code:

It would be possible to automatically check the user has permission to execute the OpenAPI operation id. File an enhancement if this is needed. Also annotations, though not recommended, can be added if so needed.

The Open API Security Provider

The authentication of a web reqyest is delegated to the OpenAPI Authenticator service. A whiteboard service is used to allow many different authentication protocols to integrate seamlessly.

Each Open API Security Provider service must register under the OpenAPIAuthenticator name and register the following service properties:

The Authentication Object

The primary responsibility of the an OpenAPI Authenticator is to authenticate a web request. For this, the provider gets access to the full Http Servlet Request- and Response. Since the authentication is a multistage process due to the complexity of the the and/or combination of the OpenAPI specification security requirements, the OpenAPI Runtime requests an Authentication object from the OpenAPI Authenticator. This Authentication object is then used in a state machine to discover which provider can authenticate the request.

The provider must implement the following methods:

Login and Logout

An authorization protocol like OAuth2 requires that the browser is redirected to an authorization server. This poses a problem for code that calls a REST API. Although code could request credentials from the user and then use a REST call to authenticate this forfeits the purpose of OAuth2 that no credentials nor personal information is ever passing through the client. (In OAuth2 terms, the client is the code that needs to perform actions for a given user.) The OAuth2 protocol achieves this by redirecting the browser to the authorization server. On that server, the user authenticates, and through another redirection a token is either given to the browser code (implict flow) or the client (code grant flow). These flows are 100% user based and do require that there is a human at the keyboard, REST calls can not help here.

For this, every authenticator is accessible through a URI on the local server.

/.openapi/security/<openapi.name>/<openapi.type>/<command>

The following commands are supported:

Single Page Web Apps

When an application is a single based web app this poses a problem. Redirection to another page closes the single page web app and this thereby loses its state, which can be expensive.

A single page web app can hardcode the logins of the available authenticator in its GUI on a general login screen, like for example:

Stackoverflow Login Window

If the user clicks on one of the authentication buttons the single page web app should open a new window or tab and set the location of that window to the proper URI of the authenticator login. For example, /.openapi/security/google/oauth2/login, assuming there is an OAuth2 authenticator configured for Google.

The OAuth2 Google authenticator will then redirect the window to the Google authorization page. Google will redirect to our authenticator and provides the token or an error. The authenticator then redirects the browser to a special closing page. The closing page should close the login window. The actual URI for the closing page is always configurable for each authenticator. That said, it is likely that only one closing page is needed for a single page web app.

Since the original single page web app is likely to be interested in the result of the login flow, the authenticator adds a number of parameters to the closing page URI. At least the following parameters are added:

It is up to the closing page how the window is closed and the result is communicated to the single page web app. One possible mechanisms is to use the HTML 5 sendMessage support. This mechanism is used in the authentication example which is part of the OpenAPI suite.

The logout flow is usually simpler. It consists of removing any authentication state from the current Http session. However, it use the same separate window flow.

Reflection on Authenticators

The OpenAPI Runtime can provide the current set of authenticators to the browser with a REST call. The URI for this call is /.openapi/security (unless configured otherwise). The result is a JSON aray with OpenAPISecurityProviderInfo objects. These objects provide sufficient information to create an automatic login page if so needed.

Included Authenticators

The OpenAPI suite provides the following authenticators. Refer to their readme.md file for features and configuration information. These authenticators can be used in industrial applications since they are hardened against known attacks.