Catalyst-Plugin-OpenIDConnect

 view release on metacpan or  search on metacpan

lib/Catalyst/Plugin/OpenIDConnect.pm  view on Meta::CPAN

use Crypt::OpenSSL::RSA;
use Crypt::PK::RSA;
use JSON::MaybeXS qw(encode_json decode_json);
use Try::Tiny;
use DateTime;
use DateTime::Format::ISO8601;
use Data::UUID;
use URI;

our $VERSION = '0.15';

=head1 NAME

Catalyst::Plugin::OpenIDConnect - OpenID Connect provider plugin for Catalyst

=head1 DESCRIPTION

A Catalyst plugin implementing the OpenID Connect specification,
providing OAuth 2.0 authentication and authorization. Note that this plugin 
does not implement the OIDC Client role; it is intended for applications 
acting as OIDC providers (authorization servers).

This plugin provides the core OpenIDConnect functionality (JWT handling, 
state management, and a reusable controller). To use it in your application, 
you must create a controller in your app's namespace that extends the plugin's 
controller (see below). This allows you to keep full control over your routing
while cooperating with ACL and other route-processing plugins.

=head1 CONFIGURATION

    package MyApp;
    use Catalyst qw/
        OpenIDConnect
        Session
        Session::Store::File
        Session::State::Cookie
    /;

    MyApp->config(
        'Plugin::OpenIDConnect' => {
            issuer => {
                url => 'http://localhost:5000',
                private_key_file => '/path/to/private.pem',
                public_key_file => '/path/to/public.pem',
                key_id => 'key-123',
            },
            clients => {
                'my-client' => {
                    client_secret => 'secret123',
                    redirect_uris => ['http://localhost:3000/callback'],
                    response_types => ['code'],
                    grant_types => ['authorization_code'],
                    scope => 'openid profile email',
                },
            },
        },
    );

=head1 CREATING THE OPENIDCONNECT CONTROLLER

To enable the OpenIDConnect endpoints, create a controller in your app that extends
the plugin's controller. Create the file C<lib/MyApp/Controller/OpenIDConnect.pm> 
(where MyApp is your app's namespace) with the following content:

    package MyApp::Controller::OpenIDConnect;

    use Moose;
    use namespace::autoclean;

    BEGIN { extends 'Catalyst::Plugin::OpenIDConnect::Controller::Root' }

    __PACKAGE__->meta->make_immutable;
    1;

Then, in your main app module, explicitly load this controller before setup:

    package MyApp;
    use Catalyst qw/
        OpenIDConnect
        Session
        Session::Store::File
        Session::State::Cookie
    /;
    
    # Load the controller before setup so Catalyst discovers it
    use MyApp::Controller::OpenIDConnect;
    
    MyApp->config(...);
    MyApp->setup(...);

Setting up the controller in this way allows you to keep full control over your
routing, and avoid namespace conflicts with ACL and other route-processing plugins.
The plugin's controller will automatically mount the standard OpenID Connect
endpoints (e.g. C</authorize>, C</token>, C</userinfo>) under the C</openidconnect>
path, so you can access them at C</openidconnect/authorize>, etc.

=head1 ROUTES ADDED TO THE APPLICATION

The plugin's controller adds the following routes to the application:

    GET  /.well-known/openid-configuration
    GET  /openidconnect/authorize
    POST /openidconnect/token
    GET  /openidconnect/userinfo
    GET  /openidconnect/jwks
    POST /openidconnect/logout

=cut

requires 'config', 'log', 'uri_for', 'user', 'request', 'response';

# Per-application-class storage for JWT and Store instances.
# Keyed by consuming application class name so that multiple Catalyst apps
# loaded in the same Perl interpreter each hold their own instances and cannot
# accidentally overwrite each other's JWT keys or stores (MED-3).
my %_oidc_jwt_by_class;
my %_oidc_store_by_class;

=head1 ATTRIBUTES

=head2 _oidc_jwt

JWT handler instance.

=cut

# Accessor method for JWT handler
sub _oidc_jwt {
    my ($self, $value) = @_;
    my $class = ref($self) || $self;
    if (defined $value) {
        die 'JWT handler must be an instance of Catalyst::Plugin::OpenIDConnect::Utils::JWT'
            unless ref $value && $value->isa('Catalyst::Plugin::OpenIDConnect::Utils::JWT');
        $_oidc_jwt_by_class{$class} = $value;
    }
    return $_oidc_jwt_by_class{$class};
}

=head2 _oidc_store

State and code storage.

=cut

# Accessor method for Store handler
sub _oidc_store {
    my ($self, $value) = @_;
    my $class = ref($self) || $self;
    if (defined $value) {
        die 'Store handler must implement Catalyst::Plugin::OpenIDConnect::Role::Store'
            unless ref $value && $value->DOES('Catalyst::Plugin::OpenIDConnect::Role::Store');
        $_oidc_store_by_class{$class} = $value;
    }
    return $_oidc_store_by_class{$class};



( run in 0.852 second using v1.01-cache-2.11-cpan-9581c071862 )