How to Use OpenIdConnect with Payara Platform to Achieve Single Sign-on
Published on 22 Apr 2021
by Rudy De BusscherWhen a user needs to access multiple applications in your environment, you should not require authentication for each application. If the user has already been authenticated for one of the applications, he or she should should not be asked for credentials when he accesses one of the other applications during the same browser session. This concept is called Single Sign-on where the authentication credentials are 'shared' in the environment and can be used by any application in that environment.
The Single Sign-on principle became more important in the last few years as the idea of microservices is also applied to the front end - and thus your big application is now a set of smaller, connected apps. When you are using Micro Frontends or Self-Contained Systems, you should consider implementing a Single Sign-on strategy.
In this blog, I describe how you can use the OpenIdConnect (OIDC) support within the Payara Platform products to achieve a Single Sign-on. This can be used when the applications are running on different servers or even on a mixture of Payara Server and Payara Micro. It requires a third-party product, the OIDC provider, which is responsible for the authorization of the user. In this example, I'm using Google, but there are many providers supported by the Payara Platform.
If all the applications are deployed on the same Payara Server domain, you can use the Single Sign-on support based on the Payara Realms. Have a look at the How to Use Single Sign-on with Payara Server Realms blog post to learn more about this approach.
OpenIdConnect Support
The Payara Platform products easily integrate with any product that supports the OpenIdConnect protocol.
Explaining OAuth2 and OIDC is not in the scope of this blog as it would require too much space, but I’ll give a very high-level introduction. You can also learn more in our user guide, Secure Your Applications with Authentication and Authorization.
OAuth2 handles the authorization and is similar to the SAML protocol but uses JSON instead of XML. With OAuth2, the application doesn’t know anything when it inspects the token. It can use it only to perform some actions against the provider who has created the token.
OIDC is built on top of this, where the created token is an idToken describing the user. It contains information about the user identity and roles and permissions it has (known by the OIDC provider). This specification is more about the authentication of the user and is not opaque, but contains the information which can be used by your application (after the signature is verified to make sure it is a valid token produced by the OIDC provider).
The configuration can be easily achieved by using the following annotations on any CDI bean:
@GoogleAuthenticationDefinition()
This annotation defines Google as the OIDC Provider. (we have also support for the other major providers or you can use the generic @OpenIdAuthenticationDefinition which requires additional configuration like the OIDC endpoints of the provider).
These annotations are available in the payara-api artefact, so this needs to be added to the project, like a Maven build file:
<dependency> <groupId>fish.payara.api</groupId> <artifactId>payara-api</artifactId> <version>5.2021.1</version> <scope>provided</scope> </dependency>
Two configuration parameters are required and define your OIDC configuration of the application in the Google Console. The ClientId and the ClientSecret can be defined as properties of the annotations but this is of course not secure and not recommended as you cannot change it easily between your different environments like development, test, and production.
Since we have integrated the MicroProfile Config support into this feature, you can use one of the more than 15 available sources where you can define the value for ClientId and ClientSecret. For more information on the MicroProfile Config support with Payara, have a look at the following documentation page.
When defined within a properties file, it can look like this:
payara.security.openid.clientId=8853612...
payara.security.openid.clientSecret=AD_uql....
Authorization
If the OIDC provider supports the maintenance of the user groups, this information can directly be extracted from the exchanged tokens between the OIDC provider and Payara. By default, it is read from the groups claim, but this can be configured using the annotation:
@OpenIdAuthenticationDefinition(
claimsDefinition = @ClaimsDefinition(callerGroupsClaim = "user-groups")
)
Google, however, doesn't support this, which means we have to provide the groups of the authenticated users ourself. Otherwise, the user will not have any roles and that is not very useful for our application.
These groups can be provided using the IdentityStore concept of the Jakarta Security API as the OIDC support within Payara is built on top of this API. When creating a custom IdentityStore, you can retrieve the groups from any system you prefer, including a database.
@ApplicationScoped public class DemoGroupsIdentityStore implements IdentityStore { @Override public Set<String> getCallerGroups(CredentialValidationResult validationResult) { // Retrieve groups based on validationResult.getCallerPrincipal() return Collections.singleton("all"); } @Override public Set<ValidationType> validationTypes() { return Collections.singleton(PROVIDE_GROUPS); } }
The mapping from Groups to Roles is done automatically on a one-to-one basis (the group name becomes the role name) or can explicitly be defined in the payara-web.xml description file using the <security-role-mapping> tag.
Configure Security Constraints
Now that we have defined the OIDC provider integration, we can use this security information to protect access to the application. This can be achieved using the web.xml file with the <security-constraint> tag to define the required roles for the resource and <security-role> tag to provide all possible roles to the system.
Or this information can be specified using annotations:
@DeclareRoles("all")
to specify all possible roles and this annotation can be placed on any CDI bean. And on a servlet, the @ServletSecurity can be used for example to indicate the required role before it can be executed:
@ServletSecurity(@HttpConstraint(rolesAllowed = "all"))
Single Sign-on
So far we have only described how to integrate the OIDC support within your application. This is because the OpenIdConnect protocol by default supports Single Sign-On. You don't need to do anything specific to have the functionality.
If you use the same ClientId and ClientSecret in multiple applications, the Single Sign-on functionality is provided by the OIDC Provider. If it detects that the user is already authenticated with the provider, it immediately calls the Callback method in your application with the necessary tokens. You only need to make sure the different applications are all registered in that same OIDC application so that the provider doesn't reject the authentication request.
The Callback method needs to be implemented by the developer but the entire handling of the tokens is already taken care of. You only need to decide what needs to happen after the user is authenticated. The following example redirects the user to the original secured page that triggered the authentication flow with the OIDC Provider:
@WebServlet("/Callback") public class Callback extends HttpServlet { @Inject private OpenIdContext context; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.sendRedirect(request.getSession().getAttribute(OpenIdConstant.ORIGINAL_REQUEST).toString()); } }
At any point in your application, you can inject the OpenIdContext to retrieve all the security-related information of the user.
Single Logout is not supported by default by all providers. When you log out of an application, the provider might not contact all the other applications using the same OIDC session to inform about this logout action. The Single Logout can be activated by setting the property notifyProvider to true, and you can define a specific page the user will be sent to after he has been logged out of the application and the OIDC session.
@OpenIdAuthenticationDefinition( logout = @LogoutDefinition(notifyProvider = true, redirectURI = "/local-logout.html") )
Conclusion
As Single Sign-On is by default supported by the OpenIdConect protocol, all applications using the same ClientId and ClientSecret will join the same Single Sign-On group. When already logged in, no authentication will be requested and the other application can be accessed immediately. The applications don't need to be deployed in the same environment, it will work as long as each application is correctly configured at the OIDC provider.
Single Sign-On can easily be achieved since the integration of the OpenIdConnect protocol is very simple with the Payara products, you just need a single annotation and two configuration values.
When you do not want to use a provider and all applications are deployed on the same Payara Server Domain, you can have a look at the Payara Single Sign-on option based on a Payara realm described in the How to Use Single Sign-on With Payara Server Realms blog.
Related Posts
Celebrating 25 Years of the CVE Program
Published on 22 Oct 2024
by Chiara Civardi
0 Comments
The Common Vulnerabilities and Exposures (CVE®) Program is celebrating its 25th anniversary today! This marks a major milestone in global cybersecurity. Since 1999, the CVE Program has been critical in helping organizations identify, manage and ...
Eclipse Foundation’s New Open Regulatory Compliance Working Group Launch
Published on 01 Oct 2024
by Dawn Baird
0 Comments