Zero Trust Security in Enterprise Java: What it is and How to Implement it
Cybersecurity isn’t just about building walls, fortresses, moats or any other external barrier anymore. Nowadays, it’s important to check […]
OpenID Connect is a security mechanism for an application to contact an identity service, verify the identity of the End-User based, and obtain End-User information using REST API’s in a secure way.
OpenID Connect extends the OAuth 2.0 protocol. In other word, OpenID Connect is a simple identity layer that sits on the top of the OAuth 2.0 protocol. OpenID Connect is needed because even though OAuth provides authorization, it does not provide authentication.
Payara Services Limited provides the out of the box support for both OAuth 2.0 (since v5.182) and OpenID Connect (since v5.183) as EE Security mechanism.
To understand how OpenID Connect works we’ll review basic key concepts such as:
OpenID Provider: Authorization Server that offers authentication as a service and providing Claims to a Relying Party about the Authentication event and the End-User.
Relying Party: OpenID Connect Client application that relies on the OpenID Provider to authenticate users and request claims about that user.
Scopes: Scopes are identifiers used to specify what access privileges are being requested. For e.g : openid, email, profile etc.
Claims: Claims are simply key & value pairs that contain information about a end-user, as well meta-information about the authentication event. For e.g : Subject, Issuing Authority, Audience, Issue Date and Expiration Date etc.
Identity Token: Identity of the End User provided by OpenID Provider to the Relying Party. The identity token contains a number of claims about that end User and also attributes about the End-User authentication event.
Access Token: Access Tokens are credentials used to access Protected Resources.
providerURI: On the OpenID Client registration, the configuration information for that OpenID Provider is retrieved from a “${providerUri}/.well-known/openid-configuration” location as a JSON document, including its Token Id, UserInfo and all other endpoint locations.
redirectURI: The Callback URL to which the Authorisation Server will redirect the Browser after authorisation has been granted by the User.
Before we dive into the application configuration details, let’s have a quick look at how OpenID works, and how we’ll interact with it.
Authorisation code flow diagram :
The OpenID Connect Provider has a number of EndPoints with which the End-User and Client Application interact. These are the Authorisation Endpoint, the Token Endpoint, the UserInfo endpoint and the JWKS endpoint.
The Authorisation endpoint is where the End-User is asked to authenticate and grant the client application consent to access their identity, and any other required information such as email, or address. Once consent is given by end-user, this endpoint passes back an Authorisation code.
The Relying party then makes a request for an ID token & Access Token to the token endpoint by exchanging the authorization code with client id & secret, to authenticate the client application for an ID token, an access token and optional refresh token.
The UserInfo Endpoint is a protected resource that is used by the OpenID Provider to return consented user information or claims to the client application, in exchange of a valid access token.
First, we need to add the following dependencies to our application:
<dependency> <groupId>fish.payara.api</groupId> <artifactId>payara-api</artifactId> <version>5.183</version>
<scope>provided</scope>
</dependency>
After the registration of application on the OpenID Connect provider and the clientId & secret should be provided by the OpenID Connect provider and you can configure your client to authenticate users for OpenID Connect provider. As Payara provides the support for OpenID Connect using EE Security mechanism so OIDC Client must be configured using @fish.payara.security.annotations.OpenIdAuthenticationDefinition annotation to define the appropriate configuration.
For example :
@OpenIdAuthenticationDefinition( providerURI = "https://sample-openid-server.com", clientId = "87068hgfg5675htfv6mrucov57bknst.apps.googleusercontent.com", clientSecret = "KfhgfhPAhId", redirectURI = "http://localhost:8080/myapp/Callback" )
OpenID Client configuration attributes can be configured via Microprofile Config way using the following properties :
Microprofile Config properties value take precedence over @OpenIdAuthenticationDefinition annotation values.
Payara also provides the in-built support for Google and Azure AD OpenID Provider using @fish.payara.security.annotations.GoogleAuthenticationDefinition and @fish.payara.security.annotations.AzureAuthenticationDefinition.
User claims, ID Token and the other authentication information can be accessed after a successful authentication from the fish.payara.security.openid.api.OpenIdContext.
In this quick guide, we learned how to configure the application for OpenID Connect security mechanism. And, as always, you can find the sample source code over on GitHub.
Additional information about OIDC: http://openid.net/specs/openid-connect-core-1_0.html
Share:
Cybersecurity isn’t just about building walls, fortresses, moats or any other external barrier anymore. Nowadays, it’s important to check […]
We’re excited to announce that Payara Platform Community 7 Beta application server is now fully certified as Jakarta EE 11 […]
Middleware runs quietly in the background of most applications, which makes it easy to overlook its lifecycle. In effect, […]
The sample application is successfully compiled but could not be deployed due to following exception
org.glassfish.deployment.common.DeploymentException: CDI deployment failure:WELD-001408: Unsatisfied dependencies for type OAuth2State with qualifiers @Default
at injection point [UnbackedAnnotatedField] @Inject private fish.payara.security.oauth2.OAuth2AuthenticationMechanism.state
at Unsatisfied dependencies for type OAuth2State with qualifiers @Default etc.
What shall I do ? I am using payara-api-5.182.jar in the library to fetch classes of Oauth2.
Make sure to add the payara-api dependency with the “provided” scope in application pom.xml as payara-api.jar distributed with Payara Server.
Does this integrate with the rest of Java EE security? For example, can I still secure my application declaratively in web.xml using security-constraint? Programmitcally using isUserInRole?
Hi David,
Payara OpenId Connect client implementation built using Jakarta EE (Java EE) 8 Security API, so yes you can secure the application programmatically and/or via declaration in deployment descriptor.
Thank you for getting back to me Gaurav Gupta. web.xml security-constraints and the “isUserInRole” method seem to be working perfectly. We may use this adapter to transition a large suite of applications to OIDC with Keycloak. It was easy to get your adapter working with Keycloak. The only trick was changing the client role mapping that Keycloak sends in the access token to something compatible with the format expected by Payara (Keycloal uses a nested JSON structure, but your adapter wants to read the role/group from a top-level attribute).
Hello again Gaurav. I think I found an issue. Your implementation cannot be used with a forward proxy. TokenController uses the JAX-RS Client and ClientBuilder, but Jersey does not use Java’s -Dhttp.proxyHost settings. This means there is no way to configure a proxy with this implementation.
I have confirmed that your implementation works fine without a proxy. And when I configure a proxy, I can see some traffic out of Payara (the phone home service), but no OIDC calls.
If you need proof I will attempt to set something up, but I was hoping you might save me some time by confirming or denying this.
“TokenController uses the JAX-RS Client and ClientBuilder, but Jersey does not use Java’s -Dhttp.proxyHost settings.”
I wrote this, but now I am not sure. I did my own test with “ClientBuilder.newClient().target(“https://example.com”).request().get().toString()”, and this works and goes through my proxy (Fiddler). But still, TokenController (https://github.com/payara/Payara/blob/master/appserver/payara-appserver-modules/security-openid/src/main/java/fish/payara/security/openid/controller/TokenController.java) uses the same API, and its requests do not show up in my proxy (Fiddler). I am stumped.
Hey David, did you manage to find a way to configure the proxy settings for OpenID?
Hi David,
Thanks for the investigation of the proxy issue, you are right. May you create the tickets here (https://github.com/payara/ecosystem-security-connectors/issues) for the Keycloal role/group compatibility and proxy config issue to track and address it.
The sample application is compiled , deployed and run against google as openid connect provider . Everything works fine. But I am unable to get any info in the callback servlet like caller name, access token etc. Every thing is null
I am using the below annotation in application
@OpenIdAuthenticationDefinition(
providerURI = “https://accounts.google.com”,
responseType = “code”,
clientId = “727311471023-seq5hcaakcb0fmlhcok5qs1t4313qirq.apps.googleusercontent.com”,
clientSecret = “mb4o7OgvrpTtGF1ZPZnbAIqk”,
redirectURI = “https://127.0.0.1:8181/OAuthProject/ProtectedServlet”,
scope = {“email”,”openid”,”profile”}
)
in redirected servlet I
@Inject OpenIdContext context;
and try to get
out.println(context.getAccessToken());
All the methods return null.
oogle authenticates properly with a challenge for email login
Where I may get possibly wrong?
Hi Kamlendu,
I can’t reproduce the issue using the above snippets, access token, identity token and claims are fetched. Can you share sample application and raise the ticket here: https://github.com/payara/ecosystem-security-connectors/issues
Hi,
everything works fine after i added the microprofile-jwt client-scope to response. Then all groups are mentioned.
But when will Payara check the token exp? In my token there are “exp”:1583314322, “iat”: 1583314262. But if the time is over everything works fine again.
context.getExpiresIn() is null.
So what do i have to do that Payara recognizes expiration?
Hi Lars,
Thanks for sharing the details, I did the investigation and found the issue that token expiry is validated only in case auto refresh token property is enabled. I have created the PR to fix it: https://github.com/payara/ecosystem-security-connectors/pull/24
Hi Gupta,
I am really struggling to implement openid connect in our project (we want to authenticate and authorize against Azure). Here are the issues we have been having:
– Depending on the Payara version, The samples can sometimes fall victim to a known race condition, fixed in later versions (sorry forgot to make note of the details)
– The samples throw this spurious WELD error in the log:
WELD-000119: Not generating any bean definitions from fish.payara.security.oidc.test.OpenIdDefaultTest because of underlying class loading error: Type com.gargoylesoftware.htmlunit.WebClient not found. If this is unexpected, enable DEBUG logging to see the full error.
I managed to hack a workaround – details to follow – but it did not seem to actually effect execution.
– The samples do not seem to like JDK 11 – again details to follow – it seems to break the useCookies test for example.
Where I really need help is with Callback and onwards:
– What does Callback actually need to contain as a minimum? The sample contains a call to getAccessToken()
– How do we get from Callback, back to where we started (/Secured in the sample, but could be many places), without hard coding a redirect? The sample deliberately does not, so it can use that as a test condition I think.
– How I configure the mapping between security groups in Azure and roles in Payara?
– I am using Krazo MVC in my application. Once I have authenticated, do I need to use JWT or other APIs or can I just use @role annotations to control access?
Thanks for all your work and help
Σ
Hi Stephen,
Redirect URL is the Callback(Servlet or RestEndpoint) location to acknowledge the successful authorization by the OpenID Connect provider. You may perform any post-authorization operation and/or forward the request to other resources based on the requirement.
You may map the OpenID token claims to Payara username/role using the configuration option claimsDefinition.callerNameClaim and claimsDefinition.callerGroupsClaim.
For example:
If OpenId claims ({ xyz.user = Gaurav, xyz.group = Admin }) have key xyz.user & xyz.group then set claimsDefinition.callerNameClaim = xyz.user & claimsDefinition.callerGroupsClaim = xyz.group. This way you can may any key of claims to Username & Group, make sure to declare all roles in deployment descriptor or using @DeclareRoles annotation.
Check out the docs for more details about the configuration options: https://docs.payara.fish/docs/5.201/documentation/payara-server/public-api/openid-connect-support.html
Payara OpenId Connect client implementation built using Jakarta EE (Java EE) 8 Security API, so yes you can secure the application programmatically using @Role annotation, HttpServletRequest.isUserInRole and/or via a declaration in the deployment descriptor.
For reporting the bug, please raise the ticket on Github and let me know If the above details are not clear.
I found that in case of using Google OpenID it is very important for redirectURI to end with slash, as google adds it on its own, the following works perfectly for me
@GoogleAuthenticationDefinition(
redirectURI=”https://localhost:8181/admin/”,
clientId=”.apps.googleusercontent.com”,
clientSecret=””,
claimsDefinition = @ClaimsDefinition(
callerNameClaim = “email”, // “name” is also okay, default one leads to ‘sub’ being used and then userPrincipal.name
// equals something like ‘118023922233344455511’
callerGroupsClaim = “groups” // default
)
)
while with
redirectURI=”https://localhost:8181/admin”
authentication worked, but userPrincipal was empty,
after a day of debugging I found that
OpenIdAuthenticationMechanism#authenticate()
compares redirectURI to requestURI and silently skips if they do not match:
String redirectURI = configuration.buildRedirectURI(request);
if (receivedState.isPresent()
&& request.getRequestURL().toString().equals(redirectURI)) {
….
}
return httpContext.doNothing();
I guess an error message should be logged in this case.
Also its worth noting this fact in the docs
https://docs.payara.fish/docs/5.201/documentation/payara-server/public-api/openid-connect-support.html
in the GoogleAuthenticationDefinition
section.
Great feature, thank you sooo much!
I’ve made roles work with
@DatabaseIdentityStoreDefinition(
dataSourceLookup = “jdbc/”,
groupsQuery = “SELECT name FROM groups WHERE user_name = ?”,
useFor = {IdentityStore.ValidationType.PROVIDE_GROUPS}
)
as it seems to be too cumbersome to set claims with Google Cloud. There seems to be no UI at Google for it, one has to use google Ffirebase API and it seems we need to set “groups” claim to a json array of strings with desired groups.
Hi,
Can You point to right direction how to implement the following scenario using OpenID and FileRealm specifications in the same app – if user is defined in one of these location, authentication can be performed successfully
Thank You
Hi Tedas,
Payara OpenID client is built on top of Jakarta EE HttpAuthenticationMechanism and currently multiple implementations of HttpAuthenticationMechanism are not allowed by Soteria. IMO this feature should be incorporated in Jakarta EE 10.
https://javaee.groups.io/g/javaee-security-spec/topic/public_review_draft/5452167?p=,,,20,0,0,0::recentpostdate%2Fsticky,,,20,2,0,5452167
Hi Gupta,
i was trying to configure keycloak, but i noticed that the documentation doesn’t show the setup images (https://docs.payara.fish/community/docs/documentation/payara-server/public-api/openid-connect-support.html).
Anyway, this article is very well written.
Thank you!
For witch reason other applications running on the same payara domain are able to discover a @OpenIdAuthenticationDefinition that is not included in that web application ?
I see payara API is at server level… but this behaviour is in contrast with other http J2ee authentications that are automatically ignored when the above definitions found…
“Activating Generic OpenID Connect authentication definition from class com.domain.security.OIDCLoginRouter”
Thanks for the comment, I would recommend posting your question to our Forum where you may be able to get more help. https://forum.payara.fish/