@Component(modules = {
PortalModule.class,
BindModule.class,
MicrometerModule.class,
AuthenticationModule.class })
public interface PortalServerComponent extends BaseComponent {
// ...
}
Currently Gentics Portal | java supports only OAuth2 authentication and authorization via Keycloak.
To enable authentication you will need to
setup the desired implementation of AuthenticationBaseHandler
in the
BootstrapInitializer
add the realm URL and client ID in the Keycloak configuration
(optional) fine tune any general authentication settings
(optional) make the Gentics Content Management Platform aware that authentication is required:
setup the UserClientHandler
in the BootstrapInitializer
setup your project in Gentics CMS to set user roles on published objects
make sure that Gentics Mesh can handle the JWTs generated by Keycloak by configuring OAuth2 and the Keycloak plugin if required
For step one, you have to
add the AuthenticationModule
to the list of modules in you projects
PortalServerComponent
(only required when you want to use the default
KeycloakAuthenticationHandler
), and
add the authentication handler implementation in the projects
BootstrapInitializer
@Component(modules = {
PortalModule.class,
BindModule.class,
MicrometerModule.class,
AuthenticationModule.class })
public interface PortalServerComponent extends BaseComponent {
// ...
}
BootstrapInitializer
// ...
@Inject
public KeycloakAuthenticationHandler authenticationHandler;
// ...
@Override
public void start() {
// Other initializations.
deployHttpVerticles(router -> {
authenticationHandler.addRoutes(router);
});
}
The third step is only necessary when the default paths for example the login or logout endpoints need to be changed, or when you wish to configure static protected paths for certain static resources or custom API endpoints.
Skipping step four in the list above means there is no fine-grained control over which pages in the Gentics CMS are accessible for which user. Authentication can still be useful in this case by specifying protected endpoints and for delivering user specific content (e.g. by using the favorite plugin).
Beware that setting up user roles in the CMS, when authentication is disabled in the portal might cause all pages to be inaccessible.
The setting authentication.useUnauthorizedRedirect
(default: false
)
determines whether the authentication handler starts the authentication
process (when the setting is false
), or fail the request with the
401 Unauthorized status and redirect to the configured 401 status page (when
the setting is true
).
Note that setting this to true
only has meaning when a 401 status page is
configured via redirects.statusPages.401
. Otherwise the authentication handler
will always start the authentication process.
The only settings needed to setup authentication via Keycloak are the realm URL and the client ID[1].
Instead of setting these values in the …/config/server.yml, you can also download the keycloak.json from the Keycloak administration console by selecting your realm, and then under Clients the client that should be used by the portal. The configuration can then be found under Installation, by selecting the Keycloak OICD JSON format. Download this file to the config directory of the portal.
The …/config/keycloak.json will look something like this:
{
"auth-server-url": "http://keycloak.mydomain.com/auth",
"confidential-port": 0,
"public-client": true,
"realm": "myproject",
"resource": "mesh-portal",
"ssl-required": "external"
}
Part of the verification of an access token issued by Keycloak is to check if
the aud
(audience) claim contains the portals client ID. Keycloak does not add
the client ID to the claim per default. For more information on how to add the
client ID see the Keycloak documentation.
For projects which do not use client roles, adding a client-scope with the hardcoded audience mapper is propably the easiest solution. See Hardcoded audience.
Setting authentication.keycloak.retryPeriodSeconds
can contain a number of
seconds, Gentics Portal | java waits for in order to retry initializing the connection to
Keycloak, in the case the initial one has failed.
If authentication.keycloak.retryPeriodSeconds
is set to greater than zero,
the Keycloak connection initialization will try to start over, if Keycloak
server is unreachable. If zero, Gentics Portal | java is shut down. If less than zero,
the old behavior (instant fail) is applied.
The default value is 60 seconds.
When the setting authentication.keycloak.rpInitiatedLogout
is enabled, the
portal will redirect the user to Keycloaks end_session_endpoint
instead of
terminating the user session in the background. This might be necessary when
Keycloak is configured as an identity broker and the configured identity
providers do not support backchannel logout.
Enabling this setting will add another authentication cookie (gpj.auth.id
)
because the users ID token is needed for the redirect back to the portal after
the Keycloak logout is finished.
When Keycloak the Gentics Portal | java are reachable under the same domain, some settings require additional consideration.
Requests to /auth should be forwarded to keycloak by a reverse proxy
All requests not starting with /auth should be forwarded to the portal
The default endpoints for login, logout and the Keycloak callback, start with /auth and must therefore be changed[2] in the server.yml to avoid conflicts with the reverse proxy setting mentioned above
Keycloak has to be
configured
with proxy-address-forwarding=true
, so that it will render its own URLs
correctly
The portal must be able to reach Keycloak with the same URL as the clients, including HTTPS access
The callback URL configured in the Keycloak options[3] must also be registered as a valid redirect URI in the client settings.
For most cases setting user roles in the CMS will suffice for authentication, but if there are any
special paths which also need to require authentication (e.g. static files, or endpoints of custom handlers), these
can be configured under authentication.protectedEndpoints
.
Each protected endpoint must specify the path which should be protected, as well as a list of necessary permissions.
The mode
can be set to ANY
or ALL
, when access is only granted when a user has at least one, or all of the
specified permissions respectively.
The path must match exactly unless it ends with an asterisk (*), which means every path that starts with the same prefix requires authentication.
authentication:
loginRedirect:
path: /auth/loggedin
defaultRedirect: /startpage.en.html
cmsPage: false
protectedEndpoints:
- path: /static/private/*
mode: ANY
permissions:
- registered_user
- admin
- path: /api/user/info
mode: ALL
permissions:
- admin
- user_admin
The configuration above makes sure that all paths under /static/private
are only available for users which have
either the registered_user
or the admin
role or both. While the endpoint /api/user/info
is only available for
users which have both the admin
and the user_admin
roles.
The UserClientHandler
which will make sure authentication credentials are passed to Mesh, is not enabled by default
(because authentication is not necessary for all projects). It has to be added in the start()
method of the
BootstrapInitializer:
BootstrapInitializer
// ...
@Inject
public UserClientHandler userClientHandler;
// ...
@Override
public void start() {
// Other initializations.
deployHttpVerticles(router -> {
userClientHandler.addRoutes(router);
});
}
To set permissions for pages in the CMS you need to create a
roles object property,
add it to the projects folders, and add it to the configuration of the Mesh content respository (the object property
is called roles
in the screenshot below):
After the next publish run (or repairing the content repository), the roles known to Mesh will be available, and can be set by changing the object property. Make sure that all folders actually have a value set, otherwise those elements will not be visible (or more precisely only visible for the admin user).
When authentication is active and an incoming request of an unauthenticated user is either
for a configured protected endpoint, or
for a page that is not available for the anonymous
role
the portal will fail the request with a 401 Unauthorized status and redirect to the respective status page, or immediately start the login process (see general configuration).
When authentication.useUnauthorizedRedirect
and a 401 Unauthorized redirect
are configured, the portal will reroute to that page, which should contain a
login link or use a portal application or custom handler which initiates
authentication.
Currently the only implementation for authentication handler is the
KeycloakAuthenticationHandler
will redirect the user to
Keycloak for authentication and
eventually returns to the portal with a
JSON Web Token (or JWT) containing
information about the user.
When the user is already authentication but just does not have the necessary permissions to access the resource, the portal will respond with a 403 Forbidden error (or display the respective error page).
When a user is authenticated by Keycloak, it will redirect the browser back to the portal, which will generate the authentication cookies and perform a final redirect.
After a successful authentication the browser is redirected a last time to the path configured as
authentication.loginRedirect.path
, which is by default /auth/loggedin
. This path can also be to a page from the CMS,
in which case the flag authentication.loginRedirect.cmsPage
should also be set to true
to enable language fallback
and make sure the configured handler will not interfere. The BindModule
must contain a binding for a
LoginRedirectHandler
which will handle requests to this path.
If the path is for a CMS page this handler will only delete the AUTH_REDIRECT
cookie after storing its value in the
routing context under the key gpj.auth.loginRedirect
. The rendered page should provide a link to the destination
stored in the cookie.
If the path is not for a CMS page, the configured handler will still delete the redirect cookie unless the
prepareRedirect()
method is overridden, but is itself responsible for using its value, and ending the request (either
with rendered markup or yet another redirect).
The reason for this procedure is that the authentication cookies should set the
SameSite policy to STRICT
. This will
prevent the browser from sending the cookie with the last requests to the portal, because the redirect chain could have
started on a different domain (the one where Keycloak is running). So the last "redirect" to the path stored in
the AUTH_REDIRECT
cookie should be initiated by the browser. For example by displaying a page with a link to the
final destination, or sending a response which will cause a page reload via JavaScript or an HTML <meta>
tag.
The portal comes with two login redirect handlers:
the ServerLoginRedirectHandler
will send a 303 See Other response to the path in the redirect cookie, and
the ClientLoginRedirectHandler
will send minimal markup where the "redirect" is triggered via a
<meta http-equiv="refresh">
tag
The client login redirect handler might need two redirects, because the first one might not contain the redirect cookie,
due to the SameSite policy. In this case the handler will cause a client redirect to itself with the added query
parameter force-redirect
. When this parameter is present, the handler will use the configured default redirect if the
cookie is not available.
Once a user has been authenticated the JWT is set as a cookie named
gpj.auth.token
, and the refresh token as the cookie named gpj.auth.refresh
.
When a request to the portal contains these cookies, its signature will be
checked, and if it is valid, the portal will consider the user as authenticated.
When the access token is expired, and the refresh token is still valid, the portal will automatically refresh the token and update the authentication cookies.
Information about the user (e.g. in a custom handler) can be acquired by getting
the User
object via RoutingContext#user()
(which is null
when the request is not
authenticated). An implementation of the AuthenticationBaseHandler
is required
to provide a User
implementation that also implements PortalUser
. This
interface provides additional convenience methods to get the username, full
name and other user information easily.
What information is stored in the JWT can be configured in the Keycloak admin
console, and is available by getting a JsonObject via the accessToken()
method.