The Internet is a global public resource that needs to be protected. Let’s start by securing the RESTful API where authenticated users can perform certain actions that unauthenticated users can’t.
This is the second part of the Learn PHP
series. If you haven't checked Build a Simple REST API in PHP, I would highly recommend you to check that article first if you want to learn how to build a REST API in core PHP.
This guide walks you through to protect the PHP Rest API endpoints with Magic.
Why Magic?
Magic enables you to ultra-secure your APIs with reliable passwordless logins, such as email magic links, social login, and WebAuthn, with just a few lines of code.
Learn more about Magic and Why Passwords Suck.
Quickstart
Visit https://magic.link/posts/magic-php
Prerequisites
Getting Started
Clone GitHub Repo
Clone the PHP Rest API if you are starting from here, but if you are following the Learn PHP
series, you are good to go. You already have all the ingredients needed for a successful recipe. We will just add some Magic
touches to it.
git clone https://github.com/shahbaz17/php-rest-api magic-php-rest-api
Build a Simple REST API in PHP
This example shows how to build a simple REST API in core PHP.
Please read https://dev.to/shahbaz17/build-a-simple-rest-api-in-php-2edl to learn more about REST API.
Prerequisites
Getting Started
Clone this project with the following commands:
git clone https://github.com/shahbaz17/php-rest-api.git
cd php-rest-api
Configure the application
Create the database and user for the project.
mysql -u root -p
CREATE DATABASE blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'rest_api_user'@'localhost' identified by 'rest_api_password';
GRANT ALL on blog.* to 'rest_api_user'@'localhost';
quit
Create the post
table.
mysql -u rest_api_user -p
// Enter your password
use blog;
CREATE TABLE `post` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`body` text NOT NULL,
`author` varchar(255
…Rest API Endpoints
-
GET /posts
: Displays all the posts frompost
table. -
GET /post/{id}
: Display a single post frompost
table. -
POST /post
: Create a post and insert intopost
table. -
PUT /post/{id}
: Update the post inpost
table. -
DELETE /post/{id}
: Delete a post frompost
table.
Configure the Database for your PHP REST API
If following the Learn PHP
series, you already have what it takes to follow this guide.
Please skip to Install the Magic Admin SDK for PHP
We will use MySQL to power our simple API.
Create a new database and user for your app:
mysql -u root -p
CREATE DATABASE blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'rest_api_user'@'localhost' identified by 'rest_api_password';
GRANT ALL on blog.* to 'rest_api_user'@'localhost';
quit
The REST API will contain posts for our Blog
Application, with the following fields: id
, title
, body
, author
, author_picture
, created_at
. It allows users to post their blog on our Blog
application.
Create the database table in MySQL.
mysql -u rest_api_user -p;
// Enter your password
use blog;
CREATE TABLE `post` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`body` text NOT NULL,
`author` varchar(255),
`author_picture` varchar(255),
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
Add the database connection variables to your .env
file:
.env
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=rest_api_user
DB_PASSWORD=rest_api_password
Install the Magic Admin SDK for PHP
The Magic SDK for server-side PHP makes it easy to leverage Decentralized ID Tokens to authenticate the users of your app.
Composer
You can install the bindings via Composer.
For example, to install Composer
on Mac OS, run the following command:
brew install composer
Once composer is installed, run the following command to get the latest Magic Admin SDK for PHP:
composer require magiclabs/magic-admin-php
Manual Installation
If you do not wish to use Composer, you can download the latest release. Then, to use the bindings, include the init.php
file.
require_once('/path/to/magic-admin-php/init.php');
Installation Dependency
The bindings require the following extensions in order to work properly:
If you use Composer, these dependencies should be handled automatically. If you install manually, you'll want to make sure that these extensions are available.
Get your Magic Secret Key
Sign Up with Magic and get your MAGIC_SECRET_KEY
.
Feel free to use the Test Application automatically configured for you, or create a new one from your Dashboard.
Update .env
Now, Add one MAGIC_SECRET_KEY
variable to the .env
file.
Updated .env
MAGIC_SECRET_KEY=sk_live_********
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=rest_api_user
DB_PASSWORD=rest_api_password
Add Magic to Post.php
Open src\Post.php
in your favourite editor.
Add getEmail()
This function is the starting point for our Magic Authentication, it instantiates Magic, validates the token, gets the issuer using the token, and retrieves the user's meta data using the issuer. It also retrieves the token from the HTTP Header.
public function getEmail() {
$did_token = \MagicAdmin\Util\Http::parse_authorization_header_value(
getallheaders()['Authorization']
);
// DIDT is missing from the original HTTP request header. Returns 404: DID Missing
if ($did_token == null) {
return $this->didMissing();
}
$magic = new \MagicAdmin\Magic(getenv('MAGIC_SECRET_KEY'));
try {
$magic->token->validate($did_token);
$issuer = $magic->token->get_issuer($did_token);
$user_meta = $magic->user->get_metadata_by_issuer($issuer);
return $user_meta->data->email;
} catch (\MagicAdmin\Exception\DIDTokenException $e) {
// DIDT is malformed.
return $this->didMissing();
}
}
Let me walk you through what this function is doing and how you can configure it for your application if you are not using PHP Rest API.
Instantiate Magic
$magic = new \MagicAdmin\Magic(getenv('MAGIC_SECRET_KEY'));
The constructor allows you to specify your API secret key and HTTP request strategy when your application is interacting with the Magic API.
Read more about Constructor and Arguments on our doc.
Retrieve <auth token>
from HTTP Header Request
$did_token = \MagicAdmin\Util\Http::parse_authorization_header_value(getallheaders()['Authorization']);
Authorization: Bearer <auth token>
Include the above code in your existing code, if you're using in your code, to grab
<auth token>
from HTTP Header Request.
In our case, we call this <auth token>
a DID Token.
if ($did_token == null) {
return $this->didMissing();
return $response;
}
If DIDT is missing from the original HTTP request header. It returns 404: DID is Malformed or Missing.
didMissing()
private function didMissing() {
$response['status_code_header'] = 'HTTP/1.1 404 Not Found';
$response['body'] = json_encode([
'error' => 'DID is Malformed or Missing.'
]);
return $response;
}
Validate DID Token <auth token>
The DID Token is generated by a Magic user on the client-side which is passed to your server via Frontend Application.
$magic->token->validate($did_token);
You should always validate the DID Token
before proceeding further. It should return nothing if the DID Token
is valid, or else it will throw a DIDTokenException
if the given DID Token is invalid or malformed.
Get the issuer
$issuer = $magic->token->get_issuer($did_token);
get_issuer
returns the Decentralized ID (iss)
of the Magic user who generated the DID Token.
Get the User Meta Data
$user_meta = $magic->user->get_metadata_by_issuer($issuer);
get_metadata_by_issuer
retrieves information about the user by the supplied iss
from the DID Token. This method is useful if you store the iss
with your user data, which is recommended.
It returns a MagicResponse
- The data field contains all of the user meta information.
-
issuer
(str): The user's Decentralized ID. -
email
(str): The user's email address. -
public_address
(str): The authenticated user's public address (a.k.a.: public key). Currently, this value is associated with the Ethereum blockchain.
-
In this guide, we will be using email
as the author in the post
table.
Update createPost()
This will be the protected route, so let's add Magic to it. It means only the authenticated persons can create a post, where it will use their email as the author field of the post.
private function createPost() {
$input = (array) json_decode(file_get_contents('php://input'), TRUE);
if (! $this->validatePost($input)) {
return $this->unprocessableEntityResponse();
}
$query = "
INSERT INTO posts
(title, body, author, author_picture)
VALUES
(:title, :body, :author, :author_picture);
";
$author = $this->getEmail();
if(is_string($author)) {
try {
$statement = $this->db->prepare($query);
$statement->execute(array(
'title' => $input['title'],
'body' => $input['body'],
'author' => $author,
'author_picture' => 'https://secure.gravatar.com/avatar/'.md5(strtolower($author)).'.png?s=200',
));
$statement->rowCount();
} catch (\PDOException $e) {
exit($e->getMessage());
}
$response['status_code_header'] = 'HTTP/1.1 201 Created';
$response['body'] = json_encode(array('message' => 'Post Created'));
return $response;
} else {
return $this->didMissing();
return $response;
}
}
Get Author's email
$author = $this->getEmail();
It returns the email id of the authenticated user.
Author's email and picture
Let's use the email address of the authenticated user to be used as the author of the post and use the email to get the public profile picture set with Gravatar.
'author' => $author,
'author_picture' => 'https://secure.gravatar.com/avatar/'.md5(strtolower($author)).'.png?s=200',
Update updatePost($id)
This route will also be protected, which means the only person who should be able to update the post is the person who wrote it.
private function updatePost($id) {
$result = $this->find($id);
if (! $result) {
return $this->notFoundResponse();
}
$input = (array) json_decode(file_get_contents('php://input'), TRUE);
if (! $this->validatePost($input)) {
return $this->unprocessableEntityResponse();
}
$author = $this->getEmail();
$query = "
UPDATE posts
SET
title = :title,
body = :body,
author = :author,
author_picture = :author_picture
WHERE id = :id AND author = :author;
";
if(is_string($author)) {
try {
$statement = $this->db->prepare($query);
$statement->execute(array(
'id' => (int) $id,
'title' => $input['title'],
'body' => $input['body'],
'author' => $author,
'author_picture' => 'https://secure.gravatar.com/avatar/'.md5(strtolower($author)).'.png?s=200',
));
if($statement->rowCount()==0) {
// Different Author trying to update.
return $this->unauthUpdate();
return $response;
}
} catch (\PDOException $e) {
exit($e->getMessage());
}
$response['status_code_header'] = 'HTTP/1.1 200 OK';
$response['body'] = json_encode(array('message' => 'Post Updated!'));
return $response;
} else {
return $this->didMissing();
return $response;
}
}
Protect unauthorize update
$query = "
UPDATE posts
SET
title = :title,
body = :body,
author = :author,
author_picture = :author_picture
WHERE id = :id AND author = :author;
";
unauthUpdate()
return $this->unauthUpdate();
.
.
.
// unauthUpdate()
private function unauthUpdate() {
$response['status_code_header'] = 'HTTP/1.1 404 Not Found';
$response['body'] = json_encode([
'error' => 'You are not authorised to delete this post.'
]);
return $response;
}
Update deletePost($id)
This route will also be protected, which means the only person who should be able to delete the post is the person who wrote it.
private function deletePost($id) {
$author = $this->getEmail();
if(is_string($author)) {
$result = $this->find($id);
if (! $result) {
return $this->notFoundResponse();
}
$query = "
DELETE FROM posts
WHERE id = :id AND author = :author;
";
try {
$statement = $this->db->prepare($query);
$statement->execute(array('id' => $id, 'author' => $author));
if($statement->rowCount()==0) {
// Different Author trying to delete.
return $this->unauthDelete();
return $response;
}
} catch (\PDOException $e) {
exit($e->getMessage());
}
$response['status_code_header'] = 'HTTP/1.1 200 OK';
$response['body'] = json_encode(array('message' => 'Post Deleted!'));
return $response;
} else {
// DID Error.
return $this->didMissing();
return $response;
}
}
Protect unauthorize delete
$query = "
DELETE FROM posts
WHERE id = :id AND author = :author;
";
unauthDelete()
return $this->unauthDelete();
.
.
.
// unauthDelete()
private function unauthDelete() {
$response['status_code_header'] = 'HTTP/1.1 404 Not Found';
$response['body'] = json_encode([
'error' => 'You are not authorised to delete this post.'
]);
return $response;
}
Get the completed Post.php
from here.
Endpoints
Available for un-authenticated
users:
-
GET /post
: Displays all the posts frompost
table. -
GET /post/{id}
: Displays a single post frompost
table.
Available for authenticated
users: Protected with Magic
-
POST /post
: Creates a post and inserts intopost
table. -
PUT /post/{id}
: Updates the post inpost
table. Also, ensures a user cannot update someone else's post. -
DELETE /post/{id}
: Deletes the post frompost
table. Also, ensures a user cannot delete someone else's post.
Development
Let's install the dependencies, start the PHP Server and test the APIs with a tool like Postman.
Install dependencies:
composer install
Run Server:
php -S localhost:8000 -t api
To get the DID Token, please follow:
shahbaz17 / magic-didtoken
Get your Magic DID Token for your APIs
Magic - Get a DID Token
Quick Start Instructions
$ git clone https://github.com/shahbaz17/magic-didtoken
$ cd magic-didtoken
$ cp .env.example .env
# enter your Magic API keys in your .env file
$ yarn install
$ yarn dev
# starts app at http://localhost:3000
Environment Variables
NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_live_
MAGIC_SECRET_KEY=sk_live_
Get your Magic Keys
Try Magic for free here and get your NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY
and MAGIC_SECRET_KEY
Or simply deploy your own on Vercel or Netlfiy.
Frontend would look like https://magic-didtoken.vercel.app
Using your API
Postman
GET /post
GET /post/{id}
POST /post
- Post Bearer Token
- Post Body
- Post Success
- Post Error: DID Token malformed or missing
PUT /post/{id}
- Post to be updated.
- Un-Authorized Update to Post
- UPDATE Success
- Post after Update
DELETE /post/{id}
Done
Congratulations!! You have successfully secured your PHP REST API with Magic.
Complete Code
shahbaz17 / magic-php-rest-api
Secure your PHP Rest API with Magic.
Secure your PHP Rest API with Magic!
This is a simple PHP REST API protected with Magic.
We will be using Magic Admin PHP SDK in this sample code to protect the PHP REST API.
The Magic Admin PHP SDK provides convenient ways for developers to interact with Magic API endpoints and an array of utilities to handle DID Token.
Please read https://dev.to/shahbaz17/secure-your-php-rest-api-with-magic-82k to learn more about securing the PHP REST API with Magic.
Prerequisites
Getting Started
Clone this project with the following commands:
git clone https://github.com/shahbaz17/magic-php-rest-api.git
cd magic-php-rest-api
Configure the application
Create the database and user for the project.
mysql -u root -p
CREATE DATABASE blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'rest_api_user'@'localhost' identified by 'rest_api_password';
GRANT ALL on blog.* to 'rest_api_user'@'localhost';
quit
Create the post
…
What's Next?
Now, as you have secured your PHP REST API with Magic. Let's deploy it on Heroku and show the world how awesome REST API you have built which is protected with the best Authentication Layer provided by Magic with just a few lines of modification to a core PHP application.
In my next article, I will cover how you can deploy your REST API application to Heroku.
Top comments (4)
Thank you for this great tutorial.
I have one question though. It's about the DID token that you use with Postman to test your API. I've spend a lot of time reading the Magic doc and exploring the dashboard, but it seems to me that at this time there's no way to get a fake DID token from the dashboard (I mean, like I was a simple user).
The only way seems to build a client-side app and use it to test our API. Am I wright ?
If this is the case, you may want to mention it in your tutorial, to avoid us to look for something that doesn't exists^^
And if I'm wrong, could you please show me where to find on the dashboard this fake DID token, so that I can test the API ?
Thank you in advance :)
Hello Yorick,
Thanks for asking this question.
As of now, there is no fake DID token. One has to build a frontend application to get a DID token to test.
For now, run:
php -S localhost:8002 -t public
If you are following github.com/shahbaz17/magic-php-res...
This will start frontend application, where you will get the DID token.
Thank you for mentioning, I will add this line to the post.
Or get a DID token from b8e51.csb.app/ for testing.
It seems you're interested in securing a PHP REST API using something referred to as "Magic." It's not entirely clear what you mean by "Magic" here, as it could be a reference to various libraries, frameworks, or security practices. I'll provide a general guide on securing a PHP REST API and highlight some commonly used practices and tools magicalkatrina. If "Magic" refers to a specific library or tool, please provide more details.