{"id":50390847,"url":"https://github.com/tfrayner/catalyst-plugin-openidconnect","last_synced_at":"2026-05-30T18:00:33.003Z","repository":{"id":352448085,"uuid":"1208711024","full_name":"tfrayner/catalyst-plugin-openidconnect","owner":"tfrayner","description":"A Catalyst plugin implementing the OIDC provider (authentication server) functionality","archived":false,"fork":false,"pushed_at":"2026-05-27T13:42:33.000Z","size":290,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-27T15:25:15.525Z","etag":null,"topics":["catalyst","catalyst-plugins","openid-connect","perl"],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tfrayner.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY_AUDIT.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-12T16:42:55.000Z","updated_at":"2026-05-27T13:41:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tfrayner/catalyst-plugin-openidconnect","commit_stats":null,"previous_names":["tfrayner/catalyst-plugin-openidconnect"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/tfrayner/catalyst-plugin-openidconnect","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tfrayner%2Fcatalyst-plugin-openidconnect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tfrayner%2Fcatalyst-plugin-openidconnect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tfrayner%2Fcatalyst-plugin-openidconnect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tfrayner%2Fcatalyst-plugin-openidconnect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tfrayner","download_url":"https://codeload.github.com/tfrayner/catalyst-plugin-openidconnect/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tfrayner%2Fcatalyst-plugin-openidconnect/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33703065,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["catalyst","catalyst-plugins","openid-connect","perl"],"created_at":"2026-05-30T18:00:30.817Z","updated_at":"2026-05-30T18:00:32.997Z","avatar_url":"https://github.com/tfrayner.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Catalyst::Plugin::OpenIDConnect\n\nA Catalyst plugin implementing the OpenID Connect specification. This plugin provides OAuth 2.0 authentication and authorization capabilities with full OIDC compliance.\n\n**Note that this is not an OIDC client plugin**. If that is what you seek, please take a look at the [Catalyst::Plugin::OIDC](https://metacpan.org/pod/Catalyst::Plugin::OIDC) module maintained elsewhere.\n\n**Full disclosure**: this plugin has been written with the aid of Claude Haiku 4.5. A human has been included in the loop throughout, closely monitoring the agent outputs, but this is an early release and mistakes may have crept through. Please create an issue on Github if you find any errors. Thank you!\n\n## Features\n\n- **Authorization Code Flow**: Full support for OpenID Connect authorization code flow\n- **Token Endpoint**: Issues ID tokens, access tokens, and refresh tokens\n- **UserInfo Endpoint**: Provides authenticated user information claims\n- **Discovery Endpoint**: OpenID Connect discovery (/.well-known/openid-configuration)\n- **JWT Handling**: Sign and verify JSON Web Tokens with RS256 algorithm\n- **State \u0026 Nonce**: CSRF protection via state parameter; nonce binding support (client must validate)\n- **Session Management**: User session tracking and token refresh\n- **Configurable**: Easy configuration via Catalyst config or external files\n- **Database Agnostic**: Works with any Catalyst ORM model\n\n## Installation\n\nInstall via cpanm:\n\n```bash\ncpanm Catalyst::Plugin::OpenIDConnect\n```\n\nOr add to your cpanfile:\n\n```\nrequires 'Catalyst::Plugin::OpenIDConnect';\n```\n\n## Quick Start\n\n### 1. Add plugin to your Catalyst app\n\n```perl\npackage MyApp;\nuse Catalyst qw/\n    -Debug\n    OpenIDConnect\n    Session\n    Session::Store::File\n    Session::State::Cookie\n/;\n```\n\n### 2. Create the OpenIDConnect controller\n\nThe plugin requires you to create a controller that extends the plugin's controller.\nCreate `lib/MyApp/Controller/OpenIDConnect.pm`:\n\n```perl\npackage MyApp::Controller::OpenIDConnect;\n\nuse Moose;\nuse namespace::autoclean;\n\nBEGIN { extends 'Catalyst::Plugin::OpenIDConnect::Controller::Root' }\n\n__PACKAGE__-\u003emeta-\u003emake_immutable;\n\n1;\n```\n\nThen load it in your main app module before setup:\n\n```perl\npackage MyApp;\nuse Catalyst qw/\n    -Debug\n    OpenIDConnect\n    Session\n    Session::Store::File\n    Session::State::Cookie\n/;\n\n# Load the controller before setup\nuse MyApp::Controller::OpenIDConnect;\n```\n\n### 3. Configure in your catalyst.conf\n\n```\n\u003cPlugin::OpenIDConnect\u003e\n    \u003cissuer\u003e\n        url = http://localhost:5000\n        private_key_file = /path/to/private_key.pem\n        public_key_file = /path/to/public_key.pem\n        key_id = my-key-123\n    \u003c/issuer\u003e\n    \n    \u003cclients\u003e\n        \u003cMyClient\u003e\n            client_id = my-client-id\n            client_secret = my-client-secret\n            redirect_uris = http://localhost:3000/callback\n            post_logout_redirect_uris = http://localhost:3000/logged-out\n            response_types = code\n            grant_types = authorization_code refresh_token\n            scope = openid profile email\n        \u003c/MyClient\u003e\n    \u003c/clients\u003e\n    \n    \u003cuser_claims\u003e\n        sub = user.id\n        username = user.username\n        name = user.name\n        email = user.email\n        picture = user.avatar_url\n    \u003c/user_claims\u003e\n\u003c/Plugin::OpenIDConnect\u003e\n```\n\n### 4. Implement a login action\n\nYour app must have a login action that supports the `back` parameter. When a user is not authenticated, the plugin redirects to your login page with a `back` parameter indicating where to return:\n\n```perl\npackage MyApp::Controller::Auth;\nuse Moose;\nuse namespace::autoclean;\n\nBEGIN { extends 'Catalyst::Controller'; }\n\nsub login : Local {\n    my ( $self, $c ) = @_;\n\n    if ( $c-\u003erequest-\u003emethod eq 'POST' ) {\n        my $username = $c-\u003erequest-\u003eparams-\u003e{username};\n        my $password = $c-\u003erequest-\u003eparams-\u003e{password};\n\n        # Validate credentials\n        if ( validate_credentials($username, $password) ) {\n            my $user = get_user($username);\n            $c-\u003esession-\u003e{user} = $user;\n\n            # IMPORTANT: Redirect to 'back' parameter to resume OIDC flow\n            my $back = $c-\u003erequest-\u003eparams-\u003e{back} || '/';\n            return $c-\u003eresponse-\u003eredirect($back);\n        }\n\n        $c-\u003estash-\u003e{error} = 'Invalid credentials';\n    }\n\n    $c-\u003estash-\u003e{template} = 'login.html';\n}\n\n1;\n```\n\n### 5. Use in your controllers\n\n```perl\npackage MyApp::Controller::Protected;\nuse Moose;\nuse namespace::autoclean;\n\nBEGIN { extends 'Catalyst::Controller'; }\n\nsub profile : Local {\n    my ( $self, $c ) = @_;\n    \n    # Check if user is authenticated via OIDC\n    unless ( $c-\u003euser ) {\n        $c-\u003eresponse-\u003eredirect( $c-\u003euri_for('/openidconnect/authorize') );\n        return;\n    }\n    \n    $c-\u003estash-\u003e{user} = $c-\u003euser;\n}\n\n1;\n```\n\n## API Endpoints\n\n### Authorization Endpoint\n\n```\nGET /openidconnect/authorize\n```\n\nParameters:\n- `response_type` (required): \"code\"\n- `client_id` (required): Client ID\n- `redirect_uri` (required): Registered redirect URI\n- `scope` (optional): Space-separated list of scopes (default: \"openid\")\n- `state` (recommended): CSRF protection token\n- `nonce` (optional): String to bind to session\n\n### Token Endpoint\n\n```\nPOST /openidconnect/token\nContent-Type: application/x-www-form-urlencoded\n```\n\nParameters:\n- `grant_type` (required): \"authorization_code\"\n- `code` (required): Authorization code\n- `client_id` (required): Client ID\n- `client_secret` (required): Client secret\n- `redirect_uri` (required): Must match the one used in authorization request\n\n### UserInfo Endpoint\n\n```\nGET /openidconnect/userinfo\nAuthorization: Bearer \u003caccess_token\u003e\n```\n\nReturns:\n```json\n{\n  \"sub\": \"user-id\",\n  \"name\": \"User Name\",\n  \"email\": \"user@example.com\",\n  \"picture\": \"https://example.com/avatar.jpg\"\n}\n```\n\n### Discovery Endpoint\n\n```\nGET /.well-known/openid-configuration\n```\n\nReturns the OpenID Connect provider configuration in JSON format.\n\n## Configuration Reference\n\n### Issuer Configuration\n\n- `url`: The issuer URL (used as 'iss' claim in tokens)\n- `private_key_file`: Path to RSA private key for signing tokens\n- `public_key_file`: Path to RSA public key for verification (auto-derived from private key if not provided)\n- `key_id`: Key identifier (used in JWT header)\n\n### Client Configuration\n\n- `client_id`: Unique client identifier\n- `client_secret`: Client secret for token endpoint\n- `redirect_uris`: Arrayref or whitespace-separated string of URIs the client is permitted to redirect to after authorization. At least one entry is required.\n- `post_logout_redirect_uris`: Arrayref or whitespace-separated string of URIs the client is permitted to redirect to after logout. Required when the client will use `post_logout_redirect_uri` at the logout endpoint.\n- `response_types`: Space-separated response types (e.g., \"code\" or \"code id_token\")\n- `grant_types`: Space-separated grant types (e.g., \"authorization_code refresh_token\")\n- `scope`: Space-separated list of scopes the client can request\n\n### User Claims Mapping\n\nMap from OpenID Connect claim names to user object attributes:\n\n```\n\u003cuser_claims\u003e\n    sub = user.id\n    name = user.display_name\n    email = user.email_address\n    email_verified = user.email_confirmed\n    phone_number = user.phone\n\u003c/user_claims\u003e\n```\n\n## Standard Claims\n\nSupported OpenID Connect standard claims:\n\n- `sub`: Unique subject identifier\n- `name`: Full name\n- `given_name`: Given (first) name\n- `family_name`: Family (last) name\n- `middle_name`: Middle name\n- `nickname`: Nickname\n- `preferred_username`: Preferred username\n- `profile`: Profile URL\n- `picture`: Picture/avatar URL\n- `website`: Website URL\n- `email`: Email address\n- `email_verified`: Whether email is verified (boolean)\n- `phone_number`: Phone number\n- `phone_number_verified`: Whether phone is verified (boolean)\n- `gender`: Gender\n- `birthdate`: Birth date (YYYY-MM-DD)\n- `zoneinfo`: Timezone\n- `locale`: Locale/language\n- `updated_at`: Profile update timestamp\n\n## Token Refresh\n\nTo refresh an access token:\n\n```perl\nmy $new_tokens = $c-\u003eopenidconnect-\u003erefresh_token(\n    client_id     =\u003e 'client-id',\n    client_secret =\u003e 'client-secret',\n    refresh_token =\u003e 'refresh-token-value'\n);\n```\n\n## Securing Endpoints\n\nUse Catalyst roles and attributes to protect endpoints:\n\n```perl\nsub profile : Local : RequireUser {\n    my ( $self, $c ) = @_;\n    # User is authenticated, $c-\u003euser is available\n}\n```\n\n## Advanced Topics\n\n### Custom Scope Handling\n\nImplement a custom scope handler:\n\n```perl\n$c-\u003eopenidconnect-\u003escope_handler(sub {\n    my ($c, $scope_string) = @_;\n    # Custom scope validation/processing\n});\n```\n\n### Custom Claims Provider\n\nProvide custom user claims:\n\n```perl\n$c-\u003eopenidconnect-\u003eclaims_provider(sub {\n    my ($c, $user) = @_;\n    return {\n        sub =\u003e $user-\u003eid,\n        name =\u003e $user-\u003efull_name,\n        custom_claim =\u003e $user-\u003esome_attribute,\n    };\n});\n```\n\n\n\n## Testing\n\nRun tests with:\n\n```bash\nprove -l t/\n```\n\n## License\n\nThis module is available under The Artistic License 2.0 (GPL Compatible). See LICENSE file for details.\n\n## Author\n\nTim F. Rayner\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftfrayner%2Fcatalyst-plugin-openidconnect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftfrayner%2Fcatalyst-plugin-openidconnect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftfrayner%2Fcatalyst-plugin-openidconnect/lists"}