Rest API Authentication in PHP Using Access Token Part 2
In the second part of this tutorial, we will add user model & controller and we will add functions for registration login & logout.
Create UserModel
Inside models, we add a new file 'UserModel' Here we have functions for storing log-in logout, and generating access tokens for users.
<?php
namespace App\Models;
use App\Database\Database as DB;
use PDO;
class UserModel
{
private $conn;
public function __construct()
{
$database = new DB;
$this->conn = $database->connect();
}
public function store($data)
{
$stmt = $this->conn->prepare('INSERT INTO users(name, email, password)
VALUES (:name, :email, :password)');
$stmt->bindParam(':name', $data['name']);
$stmt->bindParam(':email', $data['email']);
$stmt->bindParam(':password', $data['password']);
$stmt->execute();
}
public function auth($data, $login) {
$email = $data['email'];
$stmt = $this->conn->prepare("SELECT * FROM users
WHERE email = :email");
$stmt->bindParam(':email', $email);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if($user && $login) {
$user['api_key'] = $this->createApiKey($user['id']);
}
return $user;
}
public function signout($api_key, $user_id)
{
$this->removeApiKey($api_key, $user_id);
}
public function createApiKey($user_id)
{
$api_key = bin2hex(random_bytes(16));
$stmt = $this->conn->prepare('INSERT INTO api_keys(user_id, api_key)
VALUES (:user_id, :api_key)');
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':api_key', $api_key);
$stmt->execute();
return $api_key;
}
public function removeApiKey($api_key, $user_id)
{
$stmt = $this->conn->prepare("DELETE FROM api_keys
WHERE user_id = :user_id AND api_key = :api_key");
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':api_key', $api_key);
$stmt->execute();
}
public function checkIfApiKeyIsValid($api_key, $user_id)
{
$stmt = $this->conn->prepare("SELECT * FROM api_keys
WHERE user_id = :user_id AND api_key = :api_key");
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':api_key', $api_key);
$stmt->execute();
$data = $stmt->fetch(PDO::FETCH_ASSOC);
return $data;
}
}
Create UserController
Inside controllers, we add a new file 'UserController' Here we call functions already added in the 'UserModel'.
<?php
namespace App\Controllers;
use App\Models\UserModel as User;
class UserController
{
private $model;
public function __construct()
{
$this->model = new User;
}
public function register($data)
{
$user = $this->model->auth($data, $login = false);
if($user) {
echo json_encode([
'error' => true,
'message' => 'You have already an account try to log in.'
]);
}else {
$options = [
'const' => 12
];
//hash password
$password = password_hash($data['password'], PASSWORD_BCRYPT, $options);
$data['password'] = $password;
$this->model->store($data);
echo json_encode([
'message' => 'Account created successfully.'
]);
}
}
public function login($data)
{
$user = $this->model->auth($data, $login = true);
if(!$user) {
echo json_encode([
'error' => true,
'message' => 'These credentials do not match any of our records.'
]);
}else if(password_verify($data['password'], $user['password'])){
unset($user['password']);
echo json_encode([
'user' => $user,
]);
}else {
echo json_encode([
'error' => true,
'message' => 'These credentials do not match any of our records.'
]);
}
}
public function logout($data)
{
if(!$data['api_key'] || empty($data['api_key'])) {
http_response_code(401);
echo json_encode([
'error' => true,
'message' => 'unauthenticated'
]);
}else if(!$this->model->checkIfApiKeyIsValid($data['api_key'], $data['user_id'])) {
http_response_code(401);
echo json_encode([
'error' => true,
'message' => 'unauthenticated'
]);
}else {
$this->model->signout($data['api_key'], $data['user_id']);
echo json_encode([
'message' => 'Logout successfully'
]);
}
}
}
Set index.php as default
Next, to set index.php as default once the user visits our app add the following code inside the file .htaccess.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule . index.php [L]
Add routes
Next, inside the file index.php, we add routes to register login and logout users.
<?php
//autoloading classes
require_once __DIR__.'/vendor/autoload.php';
//fix cross origin blocked
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
if ($method == "OPTIONS") {
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method,Access-Control-Request-Headers, Authorization");
header("HTTP/1.1 200 OK");
die();
}
//user actions
use App\Controllers\UserController as UserController;
$user = new UserController;
//break url to parts
$segments = explode("/", parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
if($_SERVER['REQUEST_METHOD'] === 'POST' && $segments[1] === 'register' && empty($segments[2])) {
$data = (array) json_decode(file_get_contents('php://input'), true);
$user->register($data);
exit;
}else if($_SERVER['REQUEST_METHOD'] === 'POST' && $segments[1] === 'login' && empty($segments[2])) {
$data = (array) json_decode(file_get_contents('php://input'), true);
$user->login($data);
exit;
}else if($_SERVER['REQUEST_METHOD'] === 'POST' && $segments[1] === 'logout' && empty($segments[2])) {
$data = (array) json_decode(file_get_contents('php://input'), true);
$data['api_key'] = $_SERVER['HTTP_X_API_KEY'] ?? '';
$user->logout($data);
exit;
}else {
http_response_code(404);
echo json_encode([
'error' => true,
'message' => 'The page you are looking for does not exist.'
]);
}