NAME
Mojolicious::Plugin::OIDC - Mojolicious plugin for OIDC protocol integration
DESCRIPTION
This plugin makes it easy to integrate the OpenID Connect protocol into a Mojolicious application.
METHODS
register
Code executed once when the application is loaded.
Depending on the configuration, creates and keeps in memory one or more clients (OIDC::Client stateless objects) and automatically adds the callback routes to the application.
METHODS ADDED TO THE APPLICATION
oidc( $provider )
# with just one provider
my $oidc = $c->oidc;
# or
my $oidc = $c->oidc('my_provider');
# with several providers
my $oidc = $c->oidc('my_provider_1');
Creates and returns an instance of OIDC::Client::Plugin with the data from the current request and session.
If several providers are configured, the $provider parameter is mandatory.
This is the application's entry point to the library. Please see the OIDC::Client::Plugin documentation to find out what methods are available.
CONFIGURATION
Section to be added to your configuration file :
oidc_client => {
provider => {
provider_name => {
id => 'my-app-id',
secret => 'xxxxxxxxx',
well_known_url => 'https://yourprovider.com/oauth2/.well-known/openid-configuration',
signin_redirect_path => '/oidc/login/callback',
scope => 'openid profile roles email',
expiration_leeway => 20,
claim_mapping => {
login => 'sub',
lastname => 'lastName',
firstname => 'firstName',
email => 'email',
roles => 'roles',
},
audience_alias => {
other_app_name => {
audience => 'other-app-audience',
}
}
}
}
}
This is an example, see the detailed possibilities in OIDC::Client::Config.
SAMPLES
Here are some samples by category. Although you will have to adapt them to your needs, they should be a good starting point.
Setup
To setup the plugin when the application is launched :
$app->plugin('OIDC');
Authentication
To authenticate the end-user :
$app->hook(before_dispatch => sub {
my $c = shift;
my $path = $c->req->url->path;
# Public routes
return if $path =~ m[^/oidc/]
|| $path =~ m[^/error/];
# Authentication
if (my $identity = $c->oidc->get_stored_identity()) {
$c->remote_user($identity->{subject});
}
elsif (uc($c->req->method) eq 'GET' && !$c->is_ajax_request()) {
$c->oidc->redirect_to_authorize();
}
else {
$c->render(template => 'error',
message => "You have been logged out. Please try again after refreshing the page.",
status => 401);
}
});
API call
To make an API call with propagation of the security context (token exchange) :
# Retrieving a web client (Mojo::UserAgent object)
my $ua = try {
$c->oidc->build_api_useragent('other_app_name')
}
catch {
$c->log->warn("Unable to exchange token : $_");
$c->render(template => 'error',
message => "Authorization problem. Please try again after refreshing the page.",
status => 403);
return;
} or return;
# Usual call to the API
my $res = $ua->get($url)->result;
Resource Server
To check an access token from a Resource Server, assuming it's a JWT token. For example, with an application using Mojolicious::Plugin::OpenAPI, you can define a security definition that checks that the access token is intended for all the expected scopes :
$app->plugin(OpenAPI => {
url => "data:///swagger.yaml",
security => {
oidc => sub {
my ($c, $definition, $scopes, $cb) = @_;
my $claims = try {
return $c->oidc->verify_token();
}
catch {
$c->log->warn("Token validation : $_");
return;
} or return $c->$cb("Invalid or incomplete token");
foreach my $expected_scope (@$scopes) {
unless ($c->oidc->has_scope($expected_scope)) {
return $c->$cb("Insufficient scopes");
}
}
return $c->$cb();
},
}
});
Another security definition that checks that the user has at least one expected role :
$app->plugin(OpenAPI => {
url => "data:///swagger.yaml",
security => {
oidc => sub {
my ($c, $definition, $roles_to_check, $cb) = @_;
my $user = try {
$c->oidc->verify_token();
return $c->oidc->build_user_from_userinfo();
}
catch {
$c->log->warn("Token/User validation : $_");
return;
} or return $c->$cb('Unauthorized');
foreach my $role_to_check (@$roles_to_check) {
if ($user->has_role($role_to_check)) {
return $c->$cb();
}
}
return $c->$cb("Insufficient roles");
},
}
});
SECURITY RECOMMENDATION
It is highly recommended to configure the framework to store session data, including sensitive tokens such as access and refresh tokens, on the backend rather than in client-side cookies. Although cookies can be signed and encrypted, storing tokens in the client exposes them to potential security threats.