Repository navigation
Security configuration
These examples target 14.0.0 and use OpenID Connect with pac4j-oidc 6.5.9. The pac4j Config documentation explains clients, authorizers and matchers.
Register your module and configure your identity provider in conf/application.conf:
play.modules.enabled += "modules.SecurityModule"
play.http.secret.key = ${APPLICATION_SECRET}
baseUrl = "http://localhost:9000"
oidc.clientId = ${OIDC_CLIENT_ID}
oidc.secret = ${OIDC_CLIENT_SECRET}
oidc.discoveryUri = "https://identity.example.com/.well-known/openid-configuration"Set these environment variables and replace the discovery URL with your provider's endpoint. In deployment, set baseUrl to the application's public URL and register its /callback URL with the provider. Keep APPLICATION_SECRET stable across restarts and identical on all application nodes.
package modules;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import javax.inject.Singleton;
import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.oidc.client.OidcClient;
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.play.CallbackController;
import org.pac4j.play.LogoutController;
import org.pac4j.play.store.PlayCookieSessionStore;
public class SecurityModule extends AbstractModule {
@Override
protected void configure() {
final CallbackController callback = new CallbackController();
callback.setDefaultUrl("/");
bind(CallbackController.class).toInstance(callback);
final LogoutController logout = new LogoutController();
logout.setDefaultUrl("/");
logout.setDestroySession(true);
bind(LogoutController.class).toInstance(logout);
}
@Provides
@Singleton
protected Config provideConfig(com.typesafe.config.Config settings,
PlayCookieSessionStore sessionStore) {
final OidcConfiguration oidc = new OidcConfiguration();
oidc.setClientId(settings.getString("oidc.clientId"));
oidc.setSecret(settings.getString("oidc.secret"));
oidc.setDiscoveryURI(settings.getString("oidc.discoveryUri"));
final Clients clients = new Clients(settings.getString("baseUrl") + "/callback",
new OidcClient(oidc));
final Config config = new Config(clients);
config.setSessionStoreFactory(parameters -> sessionStore);
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"));
return config;
}
}package modules
import com.google.inject.{AbstractModule, Provides}
import javax.inject.Singleton
import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer
import org.pac4j.core.client.Clients
import org.pac4j.core.config.Config
import org.pac4j.oidc.client.OidcClient
import org.pac4j.oidc.config.OidcConfiguration
import org.pac4j.play.{CallbackController, LogoutController}
import org.pac4j.play.scala.{DefaultSecurityComponents, SecurityComponents}
import org.pac4j.play.store.PlayCookieSessionStore
import play.api.Configuration
class SecurityModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[SecurityComponents]).to(classOf[DefaultSecurityComponents])
val callback = new CallbackController()
callback.setDefaultUrl("/")
bind(classOf[CallbackController]).toInstance(callback)
val logout = new LogoutController()
logout.setDefaultUrl("/")
logout.setDestroySession(true)
bind(classOf[LogoutController]).toInstance(logout)
}
@Provides
@Singleton
def provideConfig(settings: Configuration, sessionStore: PlayCookieSessionStore): Config = {
val oidc = new OidcConfiguration()
oidc.setClientId(settings.get[String]("oidc.clientId"))
oidc.setSecret(settings.get[String]("oidc.secret"))
oidc.setDiscoveryURI(settings.get[String]("oidc.discoveryUri"))
val clients = new Clients(settings.get[String]("baseUrl") + "/callback", new OidcClient(oidc))
val config = new Config(clients)
config.setSessionStoreFactory(_ => sessionStore)
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"))
config
}
}Use the module for your application's language. Map your provider's roles onto pac4j profiles before using the admin authorizer; defining an authorizer does not grant users roles. The Play framework adapter supplies the default web context factory and HTTP action adapter.
The session store factory must be set on Config, as above. A Guice binding for SessionStore alone does not configure pac4j.
The examples inject this store. It serializes, compresses and encrypts pac4j data inside the Play session cookie. It clears profile login data, including access and refresh tokens, to reduce the stored profile size. Choose a cache store if your application needs to retain that data.
In v14, Play injection derives a stable AES key from play.http.secret.key. Sessions can be read after a restart and by other nodes using the same secret. No Shiro dependency is needed.
An unreadable pac4j cookie entry, including one encrypted with an old key, is discarded so the user can create a new session. The store preserves unrelated Play session entries. Changing Play's application secret also affects Play's own session signature and may invalidate the entire session.
For manual construction, supply a DataEncrypter explicitly. For example, with a Base64-encoded, persistent 16-byte AES key stored in your application's secret configuration:
byte[] key = java.util.Base64.getDecoder().decode(settings.getString("pac4j.cookie.key"));
var store = new PlayCookieSessionStore(new org.pac4j.play.store.JdkAesDataEncrypter(key));
config.setSessionStoreFactory(parameters -> store);val key = java.util.Base64.getDecoder.decode(settings.get[String]("pac4j.cookie.key"))
val store = new PlayCookieSessionStore(new org.pac4j.play.store.JdkAesDataEncrypter(key))
config.setSessionStoreFactory(_ => store)The no-argument PlayCookieSessionStore() constructor requires setDataEncrypter(...) before reading an existing cookie or writing session data. A no-argument JdkAesDataEncrypter() generates a random key and is unsuitable for retaining sessions across instances or restarts.
This store keeps pac4j data in the Play cache and an identifier in the Play session cookie. Add a Play cache implementation, for example caffeine in build.sbt.
In either provider above, replace the injected PlayCookieSessionStore parameter with org.pac4j.play.store.PlayCacheSessionStore and retain config.setSessionStoreFactory(...). For multiple application nodes, use a shared cache or a deployment arrangement that keeps a user's requests on the node holding the session. Cache expiry or eviction ends the stored session.
Continue with callback configuration, logout configuration and applying security.