{"id":16728871,"url":"https://github.com/saeven/zf3-circlical-user","last_synced_at":"2025-03-17T01:31:39.054Z","repository":{"id":9715310,"uuid":"62245250","full_name":"Saeven/zf3-circlical-user","owner":"Saeven","description":"Turnkey Authentication, Identity, and RBAC for Laminas and Zend Framework 3.  Supports Doctrine and Middleware.","archived":false,"fork":false,"pushed_at":"2024-03-21T13:28:25.000Z","size":455,"stargazers_count":37,"open_issues_count":3,"forks_count":15,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-11T14:14:28.396Z","etag":null,"topics":["acl","authentication","laminas","laminas-mvc","rbac","user"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Saeven.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-06-29T17:36:15.000Z","updated_at":"2024-12-28T16:47:18.000Z","dependencies_parsed_at":"2024-06-20T22:08:04.518Z","dependency_job_id":null,"html_url":"https://github.com/Saeven/zf3-circlical-user","commit_stats":{"total_commits":235,"total_committers":10,"mean_commits":23.5,"dds":"0.15319148936170213","last_synced_commit":"0793a9d5097ed2de6e39b1793d33d22abad2ce55"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Saeven%2Fzf3-circlical-user","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Saeven%2Fzf3-circlical-user/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Saeven%2Fzf3-circlical-user/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Saeven%2Fzf3-circlical-user/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Saeven","download_url":"https://codeload.github.com/Saeven/zf3-circlical-user/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243836015,"owners_count":20355615,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["acl","authentication","laminas","laminas-mvc","rbac","user"],"created_at":"2024-10-12T23:12:13.558Z","updated_at":"2025-03-17T01:31:38.458Z","avatar_url":"https://github.com/Saeven.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Authentication, Identity, and RBAC for the Laminas Framework\n\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ed8e71ca6eb04fc080c8e64af51629b9)](https://www.codacy.com/gh/Saeven/zf3-circlical-user/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=Saeven/zf3-circlical-user\u0026amp;utm_campaign=Badge_Grade)\n[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/ed8e71ca6eb04fc080c8e64af51629b9)](https://www.codacy.com/gh/Saeven/zf3-circlical-user/dashboard?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=Saeven/zf3-circlical-user\u0026utm_campaign=Badge_Coverage)\n[![Latest Stable Version](https://poser.pugx.org/saeven/zf3-circlical-user/v/stable)](https://packagist.org/packages/saeven/zf3-circlical-user)\n[![Total Downloads](https://poser.pugx.org/saeven/zf3-circlical-user/downloads)](https://packagist.org/packages/saeven/zf3-circlical-user)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Saeven_zf3-circlical-user\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=Saeven_zf3-circlical-user)\n[![Gitter](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/Circlical/zf3-circlical-user?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n\nPlug and play authentication, roles, resource, and action control for Laminas.\n\nQuickly Installs:\n\n- cookie based authentication (using halite and its authenticated encryption)\n- role-based access control (RBAC) with guards at the controller and action level\n- user-based access control to complement RBAC\n- resource-based permissions, giving you 'resource' and 'verb' control at the role and user level, e.g. (all administrators can 'add' a server, only Pete can 'delete')\n\nYou can see it in action, in this [ready-to-use skeleton](https://github.com/Saeven/laminas-mvc-skeleton).\n\n### Missive\n\nSure - there are other Authentication, ACL, and User modules out there. This one comes with out-of-the-box support for **Doctrine** - just plug in your user entity and go.\n\nAuthentication is persisted using cookies, meaning no session usage at all. This was done because I develop for circumstances where this is preferable, removing any need for complex or error-prone solutions for session management on an EC2 auto-scale group for example.\n\nLastly, authenticated encryption is handled using the well-trusted [Halite](https://github.com/paragonie/halite), and password hashing is properly done with PHP's new password functions.\n[Feedback always solicited on r/php.](https://www.reddit.com/r/PHP/comments/4r84jn/need_reviews_of_cookiebased_authentication_service/). If you are a paranoid fellow like me, this library should serve well!\n\nThis library works on a deny-first basis. Everything defined by its parts below, are 'allow' grants.\n\n## User Authentication\n\nThe module provides full identity/auth management, starting at the user-level. A design goal was to connect this to registration or login processes with little more than one-liners.\n\n#### Login\n\nValidate your submitted Login form, and then execute this to get your user through the door:\n\n    $user = $this-\u003eauth()-\u003eauthenticate( $emailOrUsername, $password );\n\nSuccessful authentication, will drop cookies that satisfy subsequent identity retrieval.\n\n#### Logout\n\nTrash cookies and regenerate the session key for that user, using this command:\n\n     $this-\u003eauth()-\u003eclearIdentity();\n\n## Pluggable Deny Strategy\n\nSomeone trying to do something they shouldn't? It's easy to control what happens with a pluggable DenyStrategy. Create a class that implements DenyStrategyInterface and plug it into your config. This module comes with a default **RedirectStrategy** that will send users to a login page, if the\nproblem was that there was no auth, and it wasn't an XHTTP request. Easy to use, you'd configure it like so:\n\n    'deny_strategy' =\u003e [\n\n        'class' =\u003e \\CirclicalUser\\Strategy\\RedirectStrategy::class,\n\n        'options' =\u003e [\n            'controller' =\u003e \\Application\\Controller\\LoginController::class,\n            'action' =\u003e 'index',\n        ],\n    ],\n\nWriting your own should be very simple, see provided tests.\n\n## Pluggable Password Strength Checker\n\nYou can use the built-in support for [paragonie/passwdqc](https://github.com/paragonie/passwdqc) by uncommenting the password_strength_checker config key. You can also roll your own if you have more complex needs; uncomment the key and specify your own implementation\nof [PasswordCheckerInterface](src/CirclicalUser/Provider/PasswordCheckerInterface.php). This will cause the password input routines to throw WeakPasswordExceptions when weak input is received.\n\nConfiguration of the password checker can be done two ways:\n\n#### Class without options\n\n    'password_strength_checker' =\u003e \\CirclicalUser\\Service\\PasswordChecker\\Passwdqc::class,\n\n#### Class with options\n\n    'password_strength_checker' =\u003e [\n        'implementation' =\u003e \\CirclicalUser\\Service\\PasswordChecker\\Zxcvbn::class,\n        'config' =\u003e [\n            'required_strength' =\u003e 3,\n        ],\n    ],\n\n## Creating Access For Your Users\n\nYour app needs to be modified to create a distinct auth record for each user. It's very simple.\n\n#### create \u0026 authenticate\n\nDuring user registration routines, you probably want to create the records and also log them in. To accomplish this, you can use the helper or the 'create' method on AccessService.\n\nFrom a Controller, you can use the auth plugin:\n\n     $this-\u003eauth()-\u003ecreate(User $user, string $usernameOrEmail, string $password); // controller helper\n\nor, the AuthenticationService:\n\n    $container-\u003eget(AuthenticationService::class)-\u003ecreate($user, $usernameOrEmail, $password);\n\n#### create only\n\nOtherwise, if you simply want to create a user auth record but not log them in, use:\n\n    $container-\u003eget(AuthenticationService::class)-\u003eregisterAuthenticationRecord(User $user, string $username, string $password)\n\n## Roles\n\nYour users belong to hierarchical roles that are configured in the database.  *The default guest user, is group-less.*  \nRoles are used to restrict access to **controllers**, **actions**, or **resources**.\n\n## Guards\n\nGuards are conditions on **controllers** \u0026 **actions** -- or **middleware** -- that examine **group** or **user** privileges to permit/decline attempted access. It works very similarly to [BjyAuthorize](https://github.com/bjyoungblood/BjyAuthorize)\n(a great module I used for years).\n\nConfiguring guards is very simple. Your module's config would look like so:\n\n     return [\n        'circlical' =\u003e [\n            'user' =\u003e [\n                'guards' =\u003e [\n                    'ModuleName' =\u003e [\n                        \"controllers\" =\u003e [\n                            \\Application\\Controller\\IndexController::class =\u003e [\n                                'default' =\u003e [], // anyone can access\n                            ],\n                            \\Application\\Controller\\MemberController::class =\u003e [\n                                'default' =\u003e ['user'], // specific role access\n                            ],\n                            \\Application\\Controller\\AdminController::class =\u003e [\n                                'default' =\u003e ['admin'],\n                                'actions' =\u003e [  // action-level guards\n                                    'list' =\u003e [ 'user' ], // role 'user' can access 'listAction' on AdminController\n                                ],\n                            ],\n                            \\Application\\Controller\\ComplexController::class =\u003e [\n                                'default' =\u003e ['user'],\n                                'actions' =\u003e [  // action-level guards\n                                    'save' =\u003e [\n                                        AccessService::GUARD_ACTION =\u003e 'save',      // you can lean on action/resource rules as well\n                                        AccessService::GUARD_RESOURCE =\u003e 'complex', // which call 'isAllowed' on AccessService\n                                    ],\n                                    'delete' =\u003e [\n                                        AccessService::GUARD_ROLE =\u003e 'admin',       // it is also possible to override the role requirement\n                                        AccessService::GUARD_ACTION =\u003e 'save',\n                                        AccessService::GUARD_RESOURCE =\u003e 'complex',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n     ];   \n\nIf you are defining access for [middleware route definitions](https://docs.zendframework.com/zend-mvc/middleware/#mapping-routes-to-middleware), then you don't need to configure the 'actions' section above. Further, the Module is then ignored, so you can place your middleware handler's class in any\nmodule; example:\n\n     return [\n        'circlical' =\u003e [\n            'user' =\u003e [\n                'guards' =\u003e [\n                    'Middleware' =\u003e [\n                        \"controllers\" =\u003e [\n                            \\Application\\Middleware\\MiddlewareHandler::class =\u003e [\n                                'default' =\u003e [], // anyone can access\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n     ];  \n\n## Resources \u0026 Permissions\n\nResources can be:\n\n* simple strings, or\n* anything you have that implements [ResourceInterface](src/CirclicalUser/Provider/ResourceInterface.php)\n\nBoth these usages are valid from a controller:\n\n    $this-\u003eauth()-\u003eisAllowed('door','open');\n\nor if an object:\n\n    // server implements ResourceInterface\n    $server = $serverMapper-\u003eget(142);\n    $this-\u003eauth()-\u003eisAllowed($server,'shutdown');\n\nThe AccessService is also similarly usable. See [AccessService tests](bundle/Spec/Service/AccessServiceSpec.php) for more usage examples.\n\nGranting a **role** a **permission** is done through the AccessService\n\n### User Permissions\n\nYou can also give individual users, access to specific actions on resources as well. This library provides\n**Doctrine** entities and a mapper to make this happen -- but you could wire your own [UserPermissionProviderInterface](src/CirclicalUser/Provider/UserPermissionProviderInterface.php)\nvery easily. In short, this lets the AccessService use the authenticated user to determine whether or not the logged-in individual can perform an action that supersedes what his role permissions otherwise grant. User Permissions are meant to be more permissive, not restrictive.\n\n### User API Tokens\n\nThis module also provides a utility with which to generate UserApiToken objects. See tests for usage.\n\nAdding the mapping for this entity to your User entity is very trivial\n\n    /**\n     * @ORM\\OneToMany(targetEntity=\"CirclicalUser\\Entity\\UserApiToken\", mappedBy=\"user\");\n     */\n    private $api_tokens;\n\nPulling a token to perform your own logic with it, is done with UserApiTokenMapper, e.g.\n\n    $token = $this-\u003euserApiTokenMapper-\u003eget('d0cad39b-f269-405e-b3f9-d45b349c0587');\n\nWhen it is used/consumed, you can tag it:\n\n    $token-\u003etagUse();\n\nScope (as defined by your application) is defined with bit flags\n\n    $token-\u003eaddScope(FooApi::SCOPE_QUERY);\n\n# Cookie Security\n\nYou can configure whether or not your cookies should have the secure flag set to 'true' by adjusting the auth/secure_cookies configuration value. This value accepts a boolean or closure if you need to run a discovery method on your server, perhaps, for example, to check if the current request is\ncoming through SSL.\n\n# Installation\n\n* Working with Doctrine? [Click Here](INSTALL_DOCTRINE.md)\n* Want to roll your own Providers? [Click Here](INSTALL_CUSTOM.md)\n\n# Composer Tune-Ups\n\nThis package's dependency chain depends on `doctrine/doctrine-module`, which in turn depends on `laminas/laminas-cache`.\n\nLaminas cache is wired in a strange way, and might attempt to install a ton of problematic adapters (depending on your PHP version). It is recommended that you use composer's replace to keep that mess out of your application, like so:\n\n```\n  \"replace\": {    \n    \"laminas/laminas-cache-storage-adapter-apc\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-apcu\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-blackhole\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-dba\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-ext-mongodb\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-filesystem\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-memcache\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-memcached\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-mongodb\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-redis\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-session\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-wincache\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-xcache\": \"*\",\n    \"laminas/laminas-cache-storage-adapter-zend-server\": \"*\",\n  },\n```\n\nWhat's more, since you are using this library, you probably aren't using `laminas/laminas-authentication`, which is also installed by doctrine-module. You can go ahead and throw this line into your replace block as well:\n\n```\n\"laminas/laminas-authentication\": \"*\",\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaeven%2Fzf3-circlical-user","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaeven%2Fzf3-circlical-user","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaeven%2Fzf3-circlical-user/lists"}