{"id":22290680,"url":"https://github.com/xfra35/f3-access","last_synced_at":"2025-07-28T23:31:37.758Z","repository":{"id":57083824,"uuid":"45948080","full_name":"xfra35/f3-access","owner":"xfra35","description":"Route access control for the PHP Fat-Free Framework","archived":false,"fork":false,"pushed_at":"2021-04-29T17:18:51.000Z","size":43,"stargazers_count":65,"open_issues_count":3,"forks_count":11,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-06-24T11:58:48.426Z","etag":null,"topics":["access-control","fat-free-framework","php"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xfra35.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-10T23:47:28.000Z","updated_at":"2024-04-22T20:41:31.000Z","dependencies_parsed_at":"2022-08-24T14:58:33.620Z","dependency_job_id":null,"html_url":"https://github.com/xfra35/f3-access","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/xfra35/f3-access","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xfra35%2Ff3-access","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xfra35%2Ff3-access/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xfra35%2Ff3-access/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xfra35%2Ff3-access/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xfra35","download_url":"https://codeload.github.com/xfra35/f3-access/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xfra35%2Ff3-access/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267262589,"owners_count":24061328,"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","status":"online","status_checked_at":"2025-07-26T02:00:08.937Z","response_time":62,"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":["access-control","fat-free-framework","php"],"created_at":"2024-12-03T17:13:45.948Z","updated_at":"2025-07-28T23:31:37.472Z","avatar_url":"https://github.com/xfra35.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Access\n**Route access control for the PHP Fat-Free Framework**\n\nThis plugin for [Fat-Free Framework](http://github.com/bcosca/fatfree) helps you control access to your routes.\n\n* [Requirements](#requirements)\n* [Installation](#installation)\n* [Basic usage](#basic-usage)\n* [Advanced usage](#advanced-usage)\n    * [Authorization failure](#authorization-failure)\n    * [HTTP methods access control](#http-methods-access-control)\n    * [Simple permission check](#simple-permission-check)\n* [Rules processing order](#rules-processing-order)\n    * [Path precedence](#path-precedence)\n    * [Subject precedence](#subject-precedence)\n    * [Routes uniqueness](#routes-uniqueness)\n    * [Path case insensitivity](#path-case-insensitivity)\n* [Wildcards and tokens](#wildcards-and-tokens)\n* [Named routes](#named-routes)\n* [Ini configuration](#ini-configuration)\n* [Practical configuration examples](#practical-configuration-examples)\n    * [Secure an admin area](#secure-an-admin-area)\n    * [Secure MVC-like routes](#secure-mvc-like-routes)\n    * [Secure RMR-like routes](#secure-rmr-like-routes)\n    * [Secure a members-only site](#secure-a-members-only-site)\n* [Pitfall](#pitfall)\n* [API](#api)\n* [Potential improvements](#potential-improvements)\n\n## Requirements\n\nThis plugin takes care about authorization, not authentication. So before using it, make sure you have a way to identify your app users.\n\n## Installation\n\nTo install this plugin, just copy the `lib/access.php` file into your `lib/` or your `AUTOLOAD` folder.\n\n## Basic usage\n\nInstantiate the plugin and define a default access policy (*allow* or *deny*) for your routes:\n\n```php\n$access=Access::instance();\n$access-\u003epolicy('allow'); // allow access to all routes by default\n```\n\nThen define a set of rules to protect a specific route:\n\n```php\n$access-\u003edeny('/secured.htm'); // globally deny access to /secured.htm\n$access-\u003eallow('/secured.htm','admin'); // allow \"admin\" to access /secured.htm\n```\n\nor a group of routes:\n\n```php\n// globally deny access to any URI prefixed by /protected\n$access-\u003edeny('/protected*');\n// allow \"admin\" to access any URI prefixed by /protected\n$access-\u003eallow('/protected*','admin');\n```\n\nAnd call the `authorize()` method where it fits your app best (before or after `$f3-\u003erun()`):\n\n```php\n$access-\u003eauthorize($somebody); // e.g: $somebody=$f3-\u003eget('SESSION.user')\n```\n\n### That's it!\n\nWe have restricted access to `/secured.htm` and to all the URIs starting by `/protected`.\nAny user not identified as \"admin\" will get an [error](#deny-access).\n\nBear in mind that \"admin\" can be anything meaningful to your application: a user name, group, role, right, etc..\n\nSo instead of \"admin\", we could have granted access to \"admin@domain.tld\" or \"admin-role\" or \"Can access admin area\".\n\nFor this reason, from now on we will call \"admin\" a *subject*.\n\nMultiple subjects can be addressed by a single rule:\n\n```php\n$access-\u003eallow('/foo','tic,tac,toe'); // csv string\n$access-\u003eallow('/foo',array('tic','tac','toe')); // array\n```\n\n**NB:** subject names can contain any character but commas.\n\n## Advanced usage\n\n### Authorization failure\n\nA denied access will result in a 403 error if the subject is identified or a 401 if it is not.\nIn our first example:\n* `$somebody='client'` would get a 403 error (Forbidden)\n* `$somebody=''` would get a 401 error (Unauthorized =\u003e user should authenticate first)\n\nYou can provide a callback to the `authorize()` method, which will be triggered when authorization fails:\n```php\n$access-\u003eauthorize($somebody,function($route,$subject){\n  echo \"$subject is denied access to $route\";// $route is a method followed by a path\n});\n```\nThe default behaviour (403/401) is then skipped, unless the fallback returns FALSE.\n\n### HTTP methods access control\n\nRoute permissions can be defined at HTTP method level:\n\n```php\n$access-\u003edeny('/path');\n$access-\u003eallow('GET /path');\n$access-\u003eallow('POST|PATCH|PUT|DELETE /path','admin');\n```\nIn this example, only \"admin\" can modify `/path`. Any other subject can only `GET` it.\n\n**IMPORTANT:** providing no HTTP method is equivalent to providing *all* HTTP methods (unless you're using named routes, [see below](#named-routes)).\n\nE.g:\n```php\n// the following rules are equivalent:\n$access-\u003edeny('/path');\n$access-\u003edeny('GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT /path');\n```\n\n### Simple permission check\n\nIf you need to check access permissions to a route for a specific subject, use the `granted()` method:\n```php\nif ($access-\u003egranted('/admin/part1',$somebody)) {\n  // Access granted\n} else {\n  // Access denied\n}\n```\n\nThis method performs a simple check and doesn't take any action (no error thrown).\n\n## Rules processing order\n\n### Path precedence\n\nRules are sorted from the most specific to the least specific path before being processed.\nSo the following rules:\n\n```php\n$access-\u003edeny('/admin*','mike');\n$access-\u003edeny('/admin/blog/foo','mike');\n$access-\u003eallow('/admin/blog','mike');\n$access-\u003eallow('/admin/blog/foo/bar','mike');\n$access-\u003edeny('/admin/blog/*/bar','mike');\n```\n\nare processed in the following order:\n\n```php\n$access-\u003eallow('/admin/blog/foo/bar','mike');\n$access-\u003edeny('/admin/blog/*/bar','mike');\n$access-\u003edeny('/admin/blog/foo','mike');\n$access-\u003eallow('/admin/blog','mike');\n$access-\u003edeny('/admin*','mike');\n```\n\n**IMPORTANT:** the first rule for which the path matches applies. If no rule matches, the default policy applies.\n\n### Subject precedence\n\nSpecific subject rules are processed before global rules.\nSo the following rules:\n\n```php\n$access-\u003eallow('/part2');// rule #1\n$access-\u003edeny('/part1/blog','zag');// rule #2\n$access-\u003eallow('/part1','zig,zag');// rule #3\n```\n\nare processed in the following order:\n* 2,3,1 for the subject \"zag\"\n* 3,1 for the subject \"zig\"\n\n### Routes uniqueness\n\nRules are indexed by subject name and routes, so you can't have two rules for the same subject and the same route.\nIf the case arises, the second rule erases the first:\n\n```php\n$access-\u003eallow('/part1','Dina');// rule #1\n$access-\u003edeny('/part1','Dina');// rule #2\n$access-\u003eallow('POST /part1','Dina,Misha');// rule #3\n$access-\u003edeny('/part1','Dina');// rule #4\n```\nIn this example:\n* rule #1 is ignored\n* rule #3 is ignored for Dina only (not for Misha)\n\n### Path case insensitivity\n\nFor security purposes, paths are considered case insensitive, no matter the value of the framework `CASELESS` variable.\n\nTherefore, the following rules are equivalent:\n\n```php\n$access-\u003edeny('/restricted/area');\n$access-\u003edeny('/RESTRICTED/AREA');\n$access-\u003edeny('/rEsTrIcTeD/aReA');\n```\n\n## Wildcards and tokens\n\nWildcards can be used at various places:\n\n* instead of a route verb, meaning \"any verb\": `* /path`\n  * equivalent to `/path`\n  * equivalent to `GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT /path`\n* in a route path, meaning \"any character\": `GET /foo/*/baz`\n* instead of a subject, meaning \"any subject\": `$f3-\u003eallow('/','*')`\n  * equivalent to `$f3-\u003eallow('/','')`\n  * equivalent to `$f3-\u003eallow('/')`\n\n**NB**: wildcards match empty strings, so `/admin*` match `/admin`.\n\nRoutes tokens are also supported, so `$f3-\u003eallow('/blog/@id/@slug')` is recognized.\n\nSince the plugin doesn't make use of the token names, you can as well drop them: `$f3-\u003eallow('/blog/@/@')`\n\nIn other words, `@` is a wildcard for any character which is not a forward slash,\nwhereas `*` matches everything, including forward slashes.\n\n**IMPORTANT**: read the [Pitfall](#pitfall) section.\n\n## Named routes\n\nIf you're using [named routes](https://github.com/bcosca/fatfree#named-routes),\nyou can directly refer to their aliases: `$f3-\u003eallow('@blog_entry')`;\n\nIn that case, providing no HTTP method is equivalent to providing the methods which are actually mapped to the given route. See:\n\n```php\n$f3-\u003eroute('GET|POST @admin_user_edit: /admin/user/@id','Class-\u003eedit');\n$f3-\u003eroute('DELETE @admin_user_delete: /admin/user/@id','Class-\u003edelete');\n\n// the following rules are equivalent:\n$access-\u003edeny('@admin_user_edit');\n$access-\u003edeny('GET|POST @admin_user_edit');\n$access-\u003edeny('GET|POST /admin/user/@id');\n```\n\n## Ini configuration\n\nConfiguration is possible from within an .ini file, using the `ACCESS` variable.\n\nRules should be prefixed by the keywords \"allow\" or \"deny\" (case-insensitive):\n\n```ini\n[ACCESS]\npolicy = deny ;deny all routes by default\n\n[ACCESS.rules]\nALLOW /foo = *\nALLOW /bar* = Albert,Jean-Louis\nDENY /bar/baz = Jean-Louis\n```\n\nIt works with HTTP verbs as well:\n```ini\n[ACCESS.rules]\nallow GET|POST /foo = Jim\nallow * /bar = Albert,Jim\ndeny PUT /bar = Jim\n```\n\n## Practical configuration examples\n\n### Secure an admin area\n\n```ini\n[ACCESS.rules]\nallow /admin = * ; login form\ndeny /admin/* = *\nallow /admin/* = superuser\n```\n\n### Secure MVC-like routes\n\n```ini\n[ACCESS.rules]\ndeny /*/edit = *\ndeny /*/create = *\nallow /*/edit = superuser\nallow /*/create = superuser\n```\n\n### Secure RMR-like routes\n\n```ini\n[ACCESS.rules]\ndeny * /* = *\ndeny GET /* = *\nallow POST|PUT|PATCH|DELETE /* = superuser\n```\n\n### Secure a members-only site\n\n```ini\nACCESS.policy = deny\n\n[ACCESS.rules]\nallow / = * ; login form\nallow /* = member\n```\n\n## Pitfall\n\n### Static routes overriding dynamic routes\n\nBe careful when having static routes overriding dynamic routes.\n\nAlthough not advised, the following setup is made possible by the framework:\n\n```php\n$f3-\u003eroute('GET /admin/user/@id','User-\u003eedit');\n$f3-\u003eroute('GET /admin/user/new','User-\u003ecreate');\n```\n\nFrom an authorization point of view, we may be tempted to write:\n\n```php\n$access-\u003edeny('/admin*','*');// deny access to all admin paths by default\n$access-\u003eallow('/admin/user/@id','edit_role');// allow edit_role to access /admin/user/@id\n$access-\u003eallow('/admin/user/new','create_role');// allow create_role to access /admin/user/new\n```\n\nDoing so, we might think that the `edit_role` can't access the `/admin/user/new` path, but this is an illusion.\n\nIndeed, the `@id` token match any string, including `new`.\n\nTo be convinced of this, just think that there's no difference between `/admin/user/@id` and `/admin/user/@anything`.\n\nSo in order to achieve a complete separation of roles, the correct configuration would be, in this situation:\n\n```php\n$access-\u003edeny('/admin*','*');// deny access to all admin paths by default\n$access-\u003eallow('/admin/user/@id','edit_role');// allow edit_role to access /admin/user/@id.\n$access-\u003edeny('/admin/user/new','edit_role');// ... but not /admin/user/new\n$access-\u003eallow('/admin/user/new','create_role');// allow create_role to access /admin/user/new\n```\n\nA clearer setup would be:\n\n* either to define one single path `/admin/user/@id` with `id=new` being handled inside a single controller\n* or to define two unambiguous paths, for example `/admin/user/@id` and `/admin/new-user`\n\n## API\n\n```php\n$access = Access::instance();\n```\n\n### policy( $default=NULL )\n\n**Get/set the default policy (default='allow')**\n\n```php\n$access-\u003epolicy('deny');\necho $access-\u003epolicy();// 'deny'\n```\n\n### allow( $route, $subjects='' )\n\n**Allow specified subject(s) to access a given route**\n\n```php\n$access-\u003eallow('/path'); // Grant \"all\" access to /path\n$access-\u003eallow('/path',''); // idem\n$access-\u003eallow('/path','*'); // idem\n$access-\u003eallow('POST /foo','tip-top'); // Allow \"tip-top\" to POST /foo\n```\n\n### deny( $route, $subjects='' )\n\n**Deny specified subject(s) access to a given route**\n\n```php\n$access-\u003edeny('/path'); // Deny \"all\" access to /path\n$access-\u003edeny('/path',''); // idem\n$access-\u003edeny('/path','*'); // idem\n$access-\u003edeny('POST /foo','tip-top'); // Deny \"tip-top\" access to POST /foo\n```\n\n### granted( $route, $subject='' )\n\n**Return TRUE if the given subject is granted access to the given route**\n\n```php\nif ($access-\u003egranted('/admin/part1',$somebody)) {\n  // Access granted\n} else {\n  // Access denied\n}\n```\n\nNB: you can also check access against a set of subjects. This is useful for example if you've implemented\na system of user roles or groups:\n\n```php\n$access-\u003egranted('/admin/part1',array('customer')); // FALSE\n$access-\u003egranted('/admin/part1',array('customer','admin')); // TRUE\n```\n\n### authorize( $subject='', $ondeny=NULL )\n\n**Return TRUE if the given subject is granted access to the current route**\n\nIf `$subject` is not provided, authorization is performed against \"any\" subject.\n\n`$ondeny` should be a valid F3 callback (either a PHP callable or a string)\n\n```php\n$access-\u003eauthorize(); // authorize \"any\" subject\n$access-\u003eauthorize('admin',function($route,$subject){\n  echo \"$subject is denied access to $route\";// 'admin is denied access to GET /path'\n});\n$access-\u003eauthorize('admin','My\\App-\u003eforbidden');\n```\nSee [here](#authorization-failure) for details about what happens when authorization fails.\n\nNB: you can also perform authorization against a set of subjects. This is useful for example if you've implemented\na system of user roles or groups: just pass the array of roles/groups to authorize a user. E.g:\n\n```php\n$access-\u003eauthorize(array('customer')); // unauthorized\n$access-\u003eauthorize(array('customer','admin')); // authorized\n```\n\n## Potential improvements\n\n* Think about `HEAD` and `CONNECT`: should they be authorized or consistently allowed?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxfra35%2Ff3-access","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxfra35%2Ff3-access","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxfra35%2Ff3-access/lists"}