Single file PHP script that adds a REST API to a MySQL/MariaDB, PostgreSQL, SQL Server or SQLite database.
Howto: Upload "api.php
" to your webserver, configure it to connect to your database, have an instant full-featured REST API.
NB: This is the TreeQL reference implementation in PHP.
PHP 7.2 or higher with PDO drivers enabled for one of these database systems:
MySQL 5.7 / MariaDB 10.0 or higher for spatial features in MySQL
PostgreSQL 9.5 or higher with PostGIS 2.2 or higher for spatial features
SQL Server 2017 or higher (2019 also has Linux support)
SQLite 3.16 or higher (spatial features NOT supported)
Download the "api.php
" file from the latest release:
https://github.com/mevdschee/php-crud-api/releases/latest or direct from:
https://raw.githubusercontent.com/mevdschee/php-crud-api/main/api.php
This is a single file application! Upload "api.php
" somewhere and enjoy!
For local development you may run PHP's built-in web server:
php -S localhost:8080
Test the script by opening the following URL:
http://localhost:8080/api.php/records/posts/1
Don't forget to modify the configuration at the bottom of the file.
Alternatively you can integrate this project into the web framework of your choice, see:
Automatic REST API for Laravel
Automatic REST API for Symfony 4
Automatic REST API for SlimPHP 4
In these integrations Composer is used to load this project as a dependency.
For people that don't use composer, the file "api.include.php
" is provided. This file contains everything
from "api.php
" except the configuration from "src/index.php
" and can be used by PHP's "include" function.
Edit the following lines in the bottom of the file "api.php
":
$config = new Config([ 'username' => 'xxx', 'password' => 'xxx', 'database' => 'xxx', ]);
These are all the configuration options and their default value between brackets:
"driver": mysql
, pgsql
, sqlsrv
or sqlite
(mysql
)
"address": Hostname (or filename) of the database server (localhost
)
"port": TCP port of the database server (defaults to driver default)
"username": Username of the user connecting to the database (no default)
"password": Password of the user connecting to the database (no default)
"database": Database the connecting is made to (no default)
"command": Extra SQL to initialize the database connection (none)
"tables": Comma separated list of tables to publish (defaults to 'all')
"mapping": Comma separated list of table/column mappings (no mappping)
"geometrySrid": SRID assumed when converting from WKT to geometry (4326
)
"middlewares": List of middlewares to load (cors
)
"controllers": List of controllers to load (records,geojson,openapi,status
)
"customControllers": List of user custom controllers to load (no default)
"openApiBase": OpenAPI info ({"info":{"title":"PHP-CRUD-API","version":"1.0.0"}}
)
"cacheType": TempFile
, Redis
, Memcache
, Memcached
or NoCache
(TempFile
)
"cachePath": Path/address of the cache (defaults to system's temp directory)
"cacheTime": Number of seconds the cache is valid (10
)
"jsonOptions": Options used for encoding JSON (JSON_UNESCAPED_UNICODE
)
"debug": Show errors in the "X-Exception" headers (false
)
"basePath": URI base path of the API (determined using PATH_INFO by default)
All configuration options are also available as environment variables. Write the config option with capitals, a "PHP_CRUD_API_" prefix and underscores for word breakes, so for instance:
PHP_CRUD_API_DRIVER=mysql
PHP_CRUD_API_ADDRESS=localhost
PHP_CRUD_API_PORT=3306
PHP_CRUD_API_DATABASE=php-crud-api
PHP_CRUD_API_USERNAME=php-crud-api
PHP_CRUD_API_PASSWORD=php-crud-api
PHP_CRUD_API_DEBUG=1
The environment variables take precedence over the PHP configuration.
These limitation and constrains apply:
Primary keys should either be auto-increment (from 1 to 2^53) or UUID
Composite primary and composite foreign keys are not supported
Complex writes (transactions) are not supported
Complex queries calling functions (like "concat" or "sum") are not supported
Database must support and define foreign key constraints
SQLite cannot have bigint typed auto incrementing primary keys
SQLite does not support altering table columns (structure)
The following features are supported:
Composer install or single PHP file, easy to deploy.
Very little code, easy to adapt and maintain
Supports POST variables as input (x-www-form-urlencoded)
Supports a JSON object as input
Supports a JSON array as input (batch insert)
Sanitize and validate input using type rules and callbacks
Permission system for databases, tables, columns and records
Multi-tenant single and multi database layouts are supported
Multi-domain CORS support for cross-domain requests
Support for reading joined results from multiple tables
Search support on multiple criteria
Pagination, sorting, top N list and column selection
Relation detection with nested results (belongsTo, hasMany and HABTM)
Atomic increment support via PATCH (for counters)
Binary fields supported with base64 encoding
Spatial/GIS fields and filters supported with WKT and GeoJSON
Mapping table and column names to support legacy systems
Generate API documentation using OpenAPI tools
Authentication via API key, JWT token or username/password
Database connection parameters may depend on authentication
Support for reading database structure in JSON
Support for modifying database structure using REST endpoint
Security enhancing middleware is included
Standard compliant: PSR-4, PSR-7, PSR-12, PSR-15 and PSR-17
Related projects:
PHP-CRUD-API Quick Start: A customizable, ready to go, docker compose file featuring PHP-CRUD-API.
PHP-CRUD-API filter generator: A JavaScript library creating PHP-CRUD-API filters from expressions.
JS-CRUD-API: A JavaScript client library for the API of PHP-CRUD-API
PHP-API-AUTH: Single file PHP script that is an authentication provider for PHP-CRUD-API
PHP-CRUD-UI: Single file PHP script that adds a UI to a PHP-CRUD-API project.
PHP-CRUD-ADMIN: Single file PHP script that adds a database admin interface to a PHP-CRUD-API project.
PHP-SP-API: Single file PHP script that adds a REST API to a SQL database.
dexie-mysql-sync: Synchronization between local IndexedDB and MySQL Database.
ra-data-treeql: NPM package that provides a Data Provider for React Admin.
scriptPilot/vueuse: Vue Composables in addition to VueUse.org (that support PHP-CRUD-API).
scriptPilot/add-php-backend: Add MySQL, phpMyAdmin and PHP-CRUD-API to your dev environment.
VUE-CRUD-UI: Single file Vue.js script that adds a UI to a PHP-CRUD-API project.
There are also ports of this script in:
Go-CRUD-API (work in progress)
Java JDBC by Ivan Kolchagov (v1)
Java Spring Boot + jOOQ (v2: work in progress)
There are also proof-of-concept ports of this script that only support basic REST CRUD functionality in: PHP, Java, Go, C# .net core, Node.js and Python.
You can install all dependencies of this project using the following command:
php install.php
You can compile all files into a single "api.php
" file using:
php build.php
Note that you don't use compilation when you integrate this project into another project or framework (use Composer instead).
You can access the non-compiled code at the URL:
http://localhost:8080/src/records/posts/1
The non-compiled code resides in the "src
" and "vendor
" directories. The "vendor
" directory contains the dependencies.
You can update all dependencies of this project using the following command:
php update.php
This script will install and run Composer to update the dependencies.
NB: The update script will patch the dependencies in the vendor directory for PHP 7.0 compatibility.
TreeQL allows you to create a "tree" of JSON objects based on your SQL database structure (relations) and your query.
It is loosely based on the REST standard and also inspired by json:api.
The example posts table has only a a few fields:
posts ======= id title content created
The CRUD + List operations below act on this table.
If you want to create a record the request can be written in URL format as:
POST /records/posts
You have to send a body containing:
{ "title": "Black is the new red", "content": "This is the second post.", "created": "2018-03-06T21:34:01Z" }
And it will return the value of the primary key of the newly created record:
2
To read a record from this table the request can be written in URL format as:
GET /records/posts/1
Where "1" is the value of the primary key of the record that you want to read. It will return:
{ "id": 1 "title": "Hello world!", "content": "Welcome to the first post.", "created": "2018-03-05T20:12:56Z" }
On read operations you may apply joins.
To update a record in this table the request can be written in URL format as:
PUT /records/posts/1
Where "1" is the value of the primary key of the record that you want to update. Send as a body:
{ "title": "Adjusted title!" }
This adjusts the title of the post. And the return value is the number of rows that are set:
1
If you want to delete a record from this table the request can be written in URL format as:
DELETE /records/posts/1
And it will return the number of deleted rows:
1
To list records from this table the request can be written in URL format as:
GET /records/posts
It will return:
{ "records":[ { "id": 1, "title": "Hello world!", "content": "Welcome to the first post.", "created": "2018-03-05T20:12:56Z" } ] }
On list operations you may apply filters and joins.
Filters provide search functionality, on list calls, using the "filter" parameter. You need to specify the column name, a comma, the match type, another comma and the value you want to filter on. These are supported match types:
"cs": contain string (string contains value)
"sw": start with (string starts with value)
"ew": end with (string end with value)
"eq": equal (string or number matches exactly)
"lt": lower than (number is lower than value)
"le": lower or equal (number is lower than or equal to value)
"ge": greater or equal (number is higher than or equal to value)
"gt": greater than (number is higher than value)
"bt": between (number is between two comma separated values)
"in": in (number or string is in comma separated list of values)
"is": is null (field contains "NULL" value)
You can negate all filters by prepending a "n" character, so that "eq" becomes "neq". Examples of filter usage are:
GET /records/categories?filter=name,eq,Internet GET /records/categories?filter=name,sw,Inter GET /records/categories?filter=id,le,1 GET /records/categories?filter=id,ngt,1 GET /records/categories?filter=id,bt,0,1 GET /records/categories?filter=id,in,0,1
Output:
{ "records":[ { "id": 1 "name": "Internet" } ] }
In the next section we dive deeper into how you can apply multiple filters on a single list call.
Filters can be a by applied by repeating the "filter" parameter in the URL. For example the following URL:
GET /records/categories?filter=id,gt,1&filter=id,lt,3
will request all categories "where id > 1 and id < 3". If you wanted "where id = 2 or id = 4" you should write:
GET /records/categories?filter1=id,eq,2&filter2=id,eq,4
As you see we added a number to the "filter" parameter to indicate that "OR" instead of "AND" should be applied. Note that you can also repeat "filter1" and create an "AND" within an "OR". Since you can also go one level deeper by adding a letter (a-f) you can create almost any reasonably complex condition tree.
NB: You can only filter on the requested table (not on it's included tables) and filters are only applied on list calls.
By default all columns are selected. With the "include" parameter you can select specific columns. You may use a dot to separate the table name from the column name. Multiple columns should be comma separated. An asterisk ("*") may be used as a wildcard to indicate "all columns". Similar to "include" you may use the "exclude" parameter to remove certain columns:
GET /records/categories/1?include=name GET /records/categories/1?include=categories.name GET /records/categories/1?exclude=categories.id
Output:
{ "name": "Internet" }
NB: Columns that are used to include related entities are automatically added and cannot be left out of the output.
With the "order" parameter you can sort. By default the sort is in ascending order, but by specifying "desc" this can be reversed:
GET /records/categories?order=name,desc GET /records/categories?order=id,desc&order=name
Output:
{ "records":[ { "id": 3 "name": "Web development" }, { "id": 1 "name": "Internet" } ] }
NB: You may sort on multiple fields by using multiple "order" parameters. You can not order on "joined" columns.
The "size" parameter limits the number of returned records. This can be used for top N lists together with the "order" parameter (use descending order).
GET /records/categories?order=id,desc&size=1
Output:
{ "records":[ { "id": 3 "name": "Web development" } ] }
NB: If you also want to know to the total number of records you may want to use the "page" parameter.
The "page" parameter holds the requested page. The default page size is 20, but can be adjusted (e.g. to 50).
GET /records/categories?order=id&page=1 GET /records/categories?order=id&page=1,50
Output:
{ "records":[ { "id": 1 "name": "Internet" }, { "id": 3 "name": "Web development" } ], "results": 2 }
The element "results" holds to total number of records in the table, which would be returned if no pagination would be used.
NB: Since pages that are not ordered cannot be paginated, pages will be ordered by primary key.
Let's say that you have a posts table that has comments (made by users) and the posts can have tags.
posts comments users post_tags tags ======= ======== ======= ========= ======= id id id id id title post_id username post_id name content user_id phone tag_id created message
When you want to list posts with their comments users and tags you can ask for two "tree" paths:
posts -> comments -> users posts -> post_tags -> tags
These paths have the same root and this request can be written in URL format as:
GET /records/posts?join=comments,users&join=tags
Here you are allowed to leave out the intermediate table that binds posts to tags. In this example you see all three table relation types (hasMany, belongsTo and hasAndBelongsToMany) in effect:
"post" has many "comments"
"comment" belongs to "user"
"post" has and belongs to many "tags"
This may lead to the following JSON data:
{ "records":[ { "id": 1, "title": "Hello world!", "content": "Welcome to the first post.", "created": "2018-03-05T20:12:56Z", "comments": [ { id: 1, post_id: 1, user_id: { id: 1, username: "mevdschee", phone: null, }, message: "Hi!" }, { id: 2, post_id: 1, user_id: { id: 1, username: "mevdschee", phone: null, }, message: "Hi again!" } ], "tags": [] }, { "id": 2, "title": "Black is the new red", "content": "This is the second post.", "created": "2018-03-06T21:34:01Z", "comments": [], "tags": [ { id: 1, message: "Funny" }, { id: 2, message: "Informational" } ] } ] }
You see that the "belongsTo" relationships are detected and the foreign key value is replaced by the referenced object. In case of "hasMany" and "hasAndBelongsToMany" the table name is used a new property on the object.
When you want to create, read, update or delete you may specify multiple primary key values in the URL. You also need to send an array instead of an object in the request body for create and update.
To read a record from this table the request can be written in URL format as:
GET /records/posts/1,2
The result may be:
[ { "id": 1, "title": "Hello world!", "content": "Welcome to the first post.", "created": "2018-03-05T20:12:56Z" }, { "id": 2, "title": "Black is the new red", "content": "This is the second post.", "created": "2018-03-06T21:34:01Z" } ]
Similarly when you want to do a batch update the request in URL format is written as:
PUT /records/posts/1,2
Where "1" and "2" are the values of the primary keys of the records that you want to update. The body should contain the same number of objects as there are primary keys in the URL:
[ { "title": "Adjusted title for ID 1" }, { "title": "Adjusted title for ID 2" } ]
This adjusts the titles of the posts. And the return values are the number of rows that are set:
[1,1]
Which means that there were two update operations and each of them had set one row. Batch operations use database transactions, so they either all succeed or all fail (successful ones get rolled back). If they fail the body will contain the list of error documents. In the following response the first operation succeeded and the second operation of the batch failed due to an integrity violation:
[ { "code": 0, "message": "Success" }, { "code": 1010, "message": "Data integrity violation" } ]
The response status code will always be 424 (failed dependency) in case of any failure of one of the batch operations.
To insert multiple records into this table the request can be written in URL format as:
POST /records/posts
The body should contain an array of records to be inserted:
[ { "title": "Hello world!", "content": "Welcome to the first post.", "created": "2018-03-05T20:12:56Z" }, { "title": "Black is the new red", "content": "This is the second post.", "created": "2018-03-06T21:34:01Z" } ]
The return value is also an array containing the primary keys of the newly inserted records:
[1,2]
Note that batch operation for DELETE follows the same pattern as PUT, but without a body.
For spatial support there is an extra set of filters that can be applied on geometry columns and that starting with an "s":
"sco": spatial contains (geometry contains another)
"scr": spatial crosses (geometry crosses another)
"sdi": spatial disjoint (geometry is disjoint from another)
"seq": spatial equal (geometry is equal to another)
"sin": spatial intersects (geometry intersects another)
"sov": spatial overlaps (geometry overlaps another)
"sto": spatial touches (geometry touches another)
"swi": spatial within (geometry is within another)
"sic": spatial is closed (geometry is closed and simple)
"sis": spatial is simple (geometry is simple)
"siv": spatial is valid (geometry is valid)
These filters are based on OGC standards and so is the WKT specification in which the geometry columns are represented.
Note that the SRID that is assumed when converting from WKT to geometry is specified by the config variable geometrySrid
and defaults to 4326 (WGS 84).
The GeoJSON support is a read-only view on the tables and records in GeoJSON format. These requests are supported:
method path - operation - description ---------------------------------------------------------------------------------------- GET /geojson/{table} - list - lists records as a GeoJSON FeatureCollection GET /geojson/{table}/{id} - read - reads a record by primary key as a GeoJSON Feature
The "/geojson
" endpoint uses the "/records
" endpoint internally and inherits all functionality, such as joins and filters.
It also supports a "geometry" parameter to indicate the name of the geometry column in case the table has more than one.
For map views it supports the "bbox" parameter in which you can specify upper-left and lower-right coordinates (comma separated).
The following Geometry types are supported by the GeoJSON implementation:
Point
MultiPoint
LineString
MultiLineString
Polygon
MultiPolygon
The GeoJSON functionality is enabled by default, but can be disabled using the "controllers" configuration.
To support creating an API for (a part of) a legacy system (such as Wordpress) you may want to map the table and column names as can not improve them without changing the software, while the names may need some improvement for consistency. The config allows you to rename tables and columns with a comma separated list of mappings that are split with an equal sign, like this:
'mapping' => 'wp_posts=posts,wp_posts.ID=posts.id',
This specific example will expose the "wp_posts
" table at a "posts
" end-point (instead of "wp_posts
") and the
column "ID
" within that table as the "id
" property (in lower case instead of upper case).
NB: Since these two mappings overlap the first (less specific) mapping may be omitted.
You can enable the following middleware using the "middlewares" config parameter:
"firewall": Limit access to specific IP addresses
"sslRedirect": Force connection over HTTPS instead of HTTP
"cors": Support for CORS requests (enabled by default)
"xsrf": Block XSRF attacks using the 'Double Submit Cookie' method
"ajaxOnly": Restrict non-AJAX requests to prevent XSRF attacks
"apiKeyAuth": Support for "API Key Authentication"
"apiKeyDbAuth": Support for "API Key Database Authentication"
"dbAuth": Support for "Database Authentication"
"wpAuth": Support for "Wordpress Authentication"
"jwtAuth": Support for "JWT Authentication"
"basicAuth": Support for "Basic Authentication"
"reconnect": Reconnect to the database with different parameters
"authorization": Restrict access to certain tables or columns
"validation": Return input validation errors for custom rules and default type rules
"ipAddress": Fill a protected field with the IP address on create
"sanitation": Apply input sanitation on create and update
"multiTenancy": Restricts tenants access in a multi-tenant scenario
"pageLimits": Restricts list operations to prevent database scraping
"joinLimits": Restricts join parameters to prevent database scraping
"textSearch": Search in all text fields with a simple parameter
"customization": Provides handlers for request and response customization
"json": Support read/write of JSON strings as JSON objects/arrays
"xml": Translates all input and output from JSON to XML
The "middlewares" config parameter is a comma separated list of enabled middlewares. You can tune the middleware behavior using middleware specific configuration parameters:
"firewall.reverseProxy": Set to "true" when a reverse proxy is used ("")
"firewall.allowedIpAddresses": List of IP addresses that are allowed to connect ("")
"cors.allowedOrigins": The origins allowed in the CORS headers ("*")
"cors.allowHeaders": The headers allowed in the CORS request ("Content-Type, X-XSRF-TOKEN, X-Authorization")
"cors.allowMethods": The methods allowed in the CORS request ("OPTIONS, GET, PUT, POST, DELETE, PATCH")
"cors.allowCredentials": To allow credentials in the CORS request ("true")
"cors.exposeHeaders": Whitelist headers that browsers are allowed to access ("")
"cors.maxAge": The time that the CORS grant is valid in seconds ("1728000")
"xsrf.excludeMethods": The methods that do not require XSRF protection ("OPTIONS,GET")
"xsrf.cookieName": The name of the XSRF protection cookie ("XSRF-TOKEN")
"xsrf.headerName": The name of the XSRF protection header ("X-XSRF-TOKEN")
"ajaxOnly.excludeMethods": The methods that do not require AJAX ("OPTIONS,GET")
"ajaxOnly.headerName": The name of the required header ("X-Requested-With")
"ajaxOnly.headerValue": The value of the required header ("XMLHttpRequest")
"apiKeyAuth.mode": Set to "optional" if you want to allow anonymous access ("required")
"apiKeyAuth.header": The name of the API key header ("X-API-Key")
"apiKeyAuth.keys": List of API keys that are valid ("")
"apiKeyDbAuth.mode": Set to "optional" if you want to allow anonymous access ("required")
"apiKeyDbAuth.header": The name of the API key header ("X-API-Key")
"apiKeyDbAuth.usersTable": The table that is used to store the users in ("users")
"apiKeyDbAuth.apiKeyColumn": The users table column that holds the API key ("api_key")
"dbAuth.mode": Set to "optional" if you want to allow anonymous access ("required")
"dbAuth.usersTable": The table that is used to store the users in ("users")
"dbAuth.loginTable": The table or view that is used to retrieve the users info for login ("users")
"dbAuth.usernameColumn": The users table column that holds usernames ("username")
"dbAuth.passwordColumn": The users table column that holds passwords ("password")
"dbAuth.returnedColumns": The columns returned on successful login, empty means 'all' ("")
"dbAuth.usernameFormField": The name of the form field that holds the username ("username")
"dbAuth.passwordFormField": The name of the form field that holds the password ("password")
"dbAuth.newPasswordFormField": The name of the form field that holds the new password ("newPassword")
"dbAuth.registerUser": JSON user data (or "1") in case you want the /register endpoint enabled ("")
"dbAuth.loginAfterRegistration": 1 or zero if registered users should be logged in after registration ("")
"dbAuth.passwordLength": Minimum length that the password must have ("12")
"dbAuth.sessionName": The name of the PHP session that is started ("")
"wpAuth.mode": Set to "optional" if you want to allow anonymous access ("required")
"wpAuth.wpDirectory": The folder/path where the Wordpress install can be found (".")
"wpAuth.usernameFormField": The name of the form field that holds the username ("username")
"wpAuth.passwordFormField": The name of the form field that holds the password ("password")
"jwtAuth.mode": Set to "optional" if you want to allow anonymous access ("required")
"jwtAuth.header": Name of the header containing the JWT token ("X-Authorization")
"jwtAuth.leeway": The acceptable number of seconds of clock skew ("5")
"jwtAuth.ttl": The number of seconds the token is valid ("30")
"jwtAuth.secrets": The shared secret(s) used to sign the JWT token with ("")
"jwtAuth.algorithms": The algorithms that are allowed, empty means 'all' ("")
"jwtAuth.audiences": The audiences that are allowed, empty means 'all' ("")
"jwtAuth.issuers": The issuers that are allowed, empty means 'all' ("")
"jwtAuth.sessionName": The name of the PHP session that is started ("")
"basicAuth.mode": Set to "optional" if you want to allow anonymous access ("required")
"basicAuth.realm": Text to prompt when showing login ("Username and password required")
"basicAuth.passwordFile": The file to read for username/password combinations (".htpasswd")
"basicAuth.sessionName": The name of the PHP session that is started ("")
"reconnect.driverHandler": Handler to implement retrieval of the database driver ("")
"reconnect.addressHandler": Handler to implement retrieval of the database address ("")
"reconnect.portHandler": Handler to implement retrieval of the database port ("")
"reconnect.databaseHandler": Handler to implement retrieval of the database name ("")
"reconnect.tablesHandler": Handler to implement retrieval of the table names ("")
"reconnect.mappingHandler": Handler to implement retrieval of the name mapping ("")
"reconnect.usernameHandler": Handler to implement retrieval of the database username ("")
"reconnect.passwordHandler": Handler to implement retrieval of the database password ("")
"authorization.tableHandler": Handler to implement table authorization rules ("")
"authorization.columnHandler": Handler to implement column authorization rules ("")
"authorization.pathHandler": Handler to implement path authorization rules ("")
"authorization.recordHandler": Handler to implement record authorization filter rules ("")
"validation.handler": Handler to implement validation rules for input values ("")
"validation.types": Types to enable type validation for, empty means 'none' ("all")
"validation.tables": Tables to enable type validation for, empty means 'none' ("all")
"ipAddress.tables": Tables to search for columns to override with IP address ("")
"ipAddress.columns": Columns to protect and override with the IP address on create ("")
"sanitation.handler": Handler to implement sanitation rules for input values ("")
"sanitation.types": Types to enable type sanitation for, empty means 'none' ("all")
"sanitation.tables": Tables to enable type sanitation for, empty means 'none' ("all")
"multiTenancy.handler": Handler to implement simple multi-tenancy rules ("")
"pageLimits.pages": The maximum page number that a list operation allows ("100")
"pageLimits.records": The maximum number of records returned by a list operation ("1000")
"joinLimits.depth": The maximum depth (length) that is allowed in a join path ("3")
"joinLimits.tables": The maximum number of tables that you are allowed to join ("10")
"joinLimits.records": The maximum number of records returned for a joined entity ("1000")
"textSearch.parameter": The name of the parameter used for the search term ("search")
"customization.beforeHandler": Handler to implement request customization ("")
"customization.afterHandler": Handler to implement response customization ("")
"json.controllers": Controllers to process JSON strings for ("records,geojson")
"json.tables": Tables to process JSON strings for ("all")
"json.columns": Columns to process JSON strings for ("all")
"xml.types": JSON types that should be added to the XML type attribute ("null,array")
If you don't specify these parameters in the configuration, then the default values (between brackets) are used.
In the sections below you find more information on the built-in middleware.
Currently there are five types of authentication supported. They all store the authenticated user in the $_SESSION
super global.
This variable can be used in the authorization handlers to decide wether or not somebody should have read or write access to certain tables, columns or records.
The following overview shows the kinds of authentication middleware that you can enable.
Name | Middleware | Authenticated via | Users are stored in | Session variable |
---|---|---|---|---|
API key | apiKeyAuth | 'X-API-Key' header | configuration | $_SESSION['apiKey'] |
API key DB | apiKeyDbAuth | 'X-API-Key' header | database table | $_SESSION['apiUser'] |
Database | dbAuth | '/login' endpoint | database table | $_SESSION['user'] |
Basic | basicAuth | 'Authorization' header | '.htpasswd' file | $_SESSION['username'] |
JWT | jwtAuth | 'Authorization' header | identity provider | $_SESSION['claims'] |
Below you find more information on each of the authentication types.
API key authentication works by sending an API key in a request header. The header name defaults to "X-API-Key" and can be configured using the 'apiKeyAuth.header' configuration parameter. Valid API keys must be configured using the 'apiKeyAuth.keys' configuration parameter (comma separated list).
X-API-Key: 02c042aa-c3c2-4d11-9dae-1a6e230ea95e
The authenticated API key will be stored in the $_SESSION['apiKey']
variable.
Note that the API key authentication does not require or use session cookies.
API key database authentication works by sending an API key in a request header "X-API-Key" (the name is configurable). Valid API keys are read from the database from the column "api_key" of the "users" table (both names are configurable).
X-API-Key: 02c042aa-c3c2-4d11-9dae-1a6e230ea95e
The authenticated user (with all it's properties) will be stored in the $_SESSION['apiUser']
variable.
Note that the API key database authentication does not require or use session cookies.
The database authentication middleware defines five new routes:
method path - parameters - description --------------------------------------------------------------------------------------------------- GET /me - - returns the user that is currently logged in POST /register - username, password - adds a user with given username and password POST /login - username, password - logs a user in by username and password POST /password - username, password, newPassword - updates the password of the logged in user POST /logout - - logs out the currently logged in user
A user can be logged in by sending it's username and password to the login endpoint (in JSON format).
The authenticated user (with all it's properties) will be stored in the $_SESSION['user']
variable.
The user can be logged out by sending a POST request with an empty body to the logout endpoint.
The passwords are stored as hashes in the password column in the users table. You can register a new user
using the register endpoint, but this functionality must be turned on using the "dbAuth.registerUser"
configuration parameter.
It is IMPORTANT to restrict access to the users table using the 'authorization' middleware, otherwise all users can freely add, modify or delete any account! The minimal configuration is shown below:
'middlewares' => 'dbAuth,authorization', 'authorization.tableHandler' => function ($operation, $tableName) { return $tableName != 'users'; },
Note that this middleware uses session cookies and stores the logged in state on the server.
Login using views with joined table
For login operations, it is possible to use a view as the usersTable. Such view can return a filtered result from the users table, e.g., where active = true or it may also return a result multiple tables thru a table join. At a minimum, the view should include the username and password and a field named id.
However, views with joined tables are not insertable (see issue 907 ). As a workaround, use the property loginTable to set a different reference table for login. The usersTable will still be set to the normal, insertable users table.
The Wordpress authentication middleware defines three routes:
method path - parameters - description --------------------------------------------------------------------------------------------------- GET /me - - returns the user that is currently logged in POST /login - username, password - logs a user in by username and password POST /logout - - logs out the currently logged in user
A user can be logged in by sending it's username and password to the login endpoint (in JSON format). The user can be logged out by sending a POST request with an empty body to the logout endpoint. You need to specify the Wordpress installation directory using the "wpAuth.wpDirectory" configuration parameter. The middleware calls "wp-load.php" this allows you to use Wordpress functions in the authorization middleware, like:
wp_get_current_user()
is_user_logged_in()
is_super_admin()
user_can(wp_get_current_user(),'edit_posts');
Note that the $_SESSION
variable is not used by this middleware.
The Basic type supports a file (by default '.htpasswd') that holds the users and their (hashed) passwords separated by a colon (':').
When the passwords are entered in plain text they will be automatically hashed.
The authenticated username will be stored in the $_SESSION['username']
variable.
You need to send an "Authorization" header containing a base64 url encoded version of your colon separated username and password, after the word "Basic".
Authorization: Basic dXNlcm5hbWUxOnBhc3N3b3JkMQ
This example sends the string "username1:password1".
The JWT type requires another (SSO/Identity) server to sign a token that contains claims.
Both servers share a secret so that they can either sign or verify that the signature is valid.
Claims are stored in the $_SESSION['claims']
variable. You need to send an "X-Authorization"
header containing a base64 url encoded and dot separated token header, body and signature after
the word "Bearer" (read more about JWT here). The standard says you need to
use the "Authorization" header, but this is problematic in Apache and PHP.
X-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6IjE1MzgyMDc2MDUiLCJleHAiOjE1MzgyMDc2MzV9.Z5px_GT15TRKhJCTHhDt5Z6K6LRDSFnLj8U5ok9l7gw
This example sends the signed claims:
{ "sub": "1234567890", "name": "John Doe", "admin": true, "iat": "1538207605", "exp": 1538207635 }
NB: The JWT implementation only supports the RSA and HMAC based algorithms.