Tutorial Membuat API PHP Dasar Dengan Otentikasi Token - CRUDPRO

Tutorial Membuat API PHP Dasar Dengan Otentikasi Token

Tutorial Membuat API PHP Dasar Dengan Otentikasi Token

Hari ini kita akan membuat API dengan PHP dan otentikasi menggunakan token dalam waktu sesingkat mungkin. Mari, belajarlah bersamaku melewati dunia scripting, hack and slash yang menyenangkan.

kata motivasi: Ingat-ingatlah jika pemrograman lebih dari sekedar teknik, ini adalah kreativitas, ini ialah keterampilan. Anda akan jadi lebih baik tiap hari sepanjang Anda terus latihan dan menuntaskan permasalahan dengan kode Anda.

Silahkan kita mulai dengan asumsi jika Anda menggunakan server Apache http dan php 8.x (terang pada akun hosting di Winkhosting.co), atau Anda telah mempunyai lingkungan yang dikonfigurasi pada penyedia lain atau PC Anda dan semua berperan secara benar (jika Anda menggunakan server situs lain seperti nginx atau caddy, Anda harus cari pilihan untuk menimpa track).

Untuk melanjutkan, saat ini Anda memiliki lingkungan tempat Anda bisa bekerja, API kami akan membutuhkan management track atau "titik akhir", silahkan awali dengan membuat susunan dan peranan management track.

Perutean, mod_rewrite, dan.htaccess

Mulai dengan membuat directory, misalkan, dalam kasus saya, saya sudah membuat directory /simple-api.

Di directory buat file namanya.htaccess dan didalamnya catat/tempel baris berikut ini:

<Files "*.php">
    Require ip 127.0.0.1
</Files>

<Files "index.php">
    Require all granted
</Files>

RewriteEngine On
#Remove the comments to force https, you must have an SSL certificate.
#RewriteCond %{HTTPS} off
#RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

#Enable HTTP Authorization Header if you use php-cgi 
# $_SERVER["HTTP_AUTHORIZATION"]
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

RewriteBase /simple-api/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]

Mari telusuri bagian-bagian file untuk menjelaskan apa yang masing-masing lakukan:

Pembatasan akses skrip PHP

<Files "*.php">
    Require ip 127.0.0.1
</Files>

<Files "index.php">
    Require all granted
</Files>

Saya menggunakan bagian ini untuk tujuan keamanan, yang mereka nyatakan adalah bahwa satu-satunya file .php yang dapat diminta atau "diakses" adalah index.php yang akan bertanggung jawab mengelola rute ke berbagai titik akhir API kami (jika Anda tidak tahu konsep endpoint, bisa cek disini).

Singkatnya, yang kami tunjukkan ke server http Apache adalah:

  • Mencegah akses ke file dengan ekstensi .php ke semua sumber kecuali 127.0.0.1, alamat localhost di IPv4, jika server Anda sudah menggunakan IPv6, Anda perlu menambahkan IP ::1. Ini berarti bahwa hanya permintaan lokal yang dapat dibuat untuk skrip di direktori.
  • Mengotorisasi kueri dan permintaan dari sumber apa pun (IP sumber) ke file index.php.

Memaksa pengalihan traffic ke HTTPS (Dengan sertifikat SSL)

Kami melanjutkan aturan untuk menggunakan https di API Anda, ini akan direkomendasikan dalam produksi, dalam pengujian Anda dapat menggunakan http tanpa masalah.

RewriteEngine On
#Remove the comments to force https, you must have an SSL certificate.
#RewriteCond %{HTTPS} off
#RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

Baris pertama file aktifkan mesin penulisan track dengan nilai "Aktif", baris berikut bertanggung jawab untuk mengganti traffic dan melanjutkan dari URL yang diawali di http ke https yang tepat. Saya tidak menguraikan mengenai keadaan peralihan, topik ini sudah ambil di semua buku, jika Anda ingin mengetahui semakin banyak, periksa dokumentasi Apache di sini

Peringatan:Umumnya mod_rewrite diaktifkan secara standar di server Apache, bila Anda terima semua tipe kesalahan terkait dengan tidak ada modul ini, Anda harus minta dukungan dari penyedia hosting untuk aktifkan atau aktifkan di lingkungan Anda seperti mestinya.

Header Otorisasi HTTP dan php-cgi

#Enable HTTP Authorization Header if you use php-cgi 
# $_SERVER["HTTP_AUTHORIZATION"]
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

Baris-baris ini opsional jika Anda tidak menggunakan php sebagai cgi (php-cgi), rekomendasi saya dari lubuk hati saya yang terdalam adalah menggunakannya dalam hal apa pun kecuali jika itu menghasilkan semacam kesalahan. Apa yang diizinkan baris ini adalah aktivasi dan penyimpanan informasi autentikasi dalam array $_SERVER PHP, dengan kunci "HTTP_AUTHORIZATION".

Jika Anda berencana untuk menggunakan autentikasi dasar melalui header, ini akan menjadi persyaratan bagi PHP untuk menerima informasi yang diperlukan.

Penulisan rute, rute dasar, dan file index.php

RewriteBase /simple-api/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]

Baris terakhir ini melakukan keajaiban pengiriman track apa pun di dalam /simple-api/ ke file index.php, informasi track akan diproses oleh file ini dan kita akan melihatnya nanti.

Akhirnya, file .htaccess kami telah dibuat dan kami siap untuk melanjutkan jalan menyenangkan pemrograman PHP.

Pengolahan rute

Mari kita mulai dengan membuat file index.php, di dalam direktori yang sama /simple-api, lalu buka tag php seperti yang diajarkan di sekolah, tidak ada tag pendek, kami bukan programmer vulgar:

	<?php

Kata motivasi: Jangan gunakan tag penutup php kecuali benar-benar diperlukan untuk konteks penggunaan skrip Anda. Terima saya nanti.

Kami akan menentukan beberapa variabel awal untuk mengelola titik akhir kami, track dasar dan parameter yang diterima dalam permintaan:

<?php

$BASE_URI = "/simple-api/";

$endpoints = array();
$requestData = array();

$parsedURI = parse_url($_SERVER["REQUEST_URI"]);
$endpointName = str_replace($BASE_URI, "", $parsedURI["path"]);

if (empty($endpointName)) {
    $endpointName = "/";
}

BASE_URI cocok dengan track dasar directory kami, tiap permintaan akan berisi text ini pada awal, sama yang kami gunakan di file .htaccess.

$endPoints akan berisi fungsi anonim (penutupan) dengan logika untuk mengolah permintaan tertentu. Kami menggunakan penutupan untuk kurangi kesukaran dan jumlah kode dalam contoh ini, tapi dianjurkan untuk menggunakan class atau memisahkan logika tiap titik akhirnya pengatasan track.

$endpointName adalah nama titik akhir yang ditanya dalam permintaan, ditetapkan dari "REQUEST_URI" dalam array PHP $_SERVER, pertama kali string text dirinci menggunakan parse_url() dan path dan string queri diekstraksi dengan get parameter bila berlaku untuk permintaan, karena itu pergantian text dilaksanakan untuk hapus string dari URI track dasar. Dengan langkah ini kita cuma memperoleh track titik akhir.

Jika faktor ini kosong, kami akan pahami jika mereka coba memasuki track dasar API dan kami tentukan nama titik akhir standar "/", ini akan membantu kami nanti untuk mengetahui skenario ini dan meresponsnya dengan tepat.

Saat ini mari kita rincikan tiga logika titik akhir:

  • track dasar, yaitu saat pengguna mencoba melakukan kueri /simple-api/ secara langsung tanpa menggunakan titik akhir tertentu.
  • “sayhello”, titik akhir ini akan menerima parameter “nama” dan mengembalikan respons dengan teks “hallo!
  • “404”, ini tidak akan digunakan secara langsung, ini akan berfungsi untuk menanggapi nama titik akhir lainnya yang tidak ada di API dengan pesan default.
// closures to define each endpoint logic, 
// I know, this can be improved with some OOP but this is a basic example, 
// don't do this at home, well, or if you want to do it, don't feel judged.

/**
 * prints a default message if the API base path is queried.
 * @param array $requestData contains the parameters sent in the request, for this endpoint they are ignored.
 * @return void
 */
$endpoints["/"] = function (array $requestData): void {

    echo json_encode("Welcome to my API!");
};

/**
 * prints a greeting message with the name specified in the $requestData["name"] item.
 * if the variable is empty a default name is used.
 * @param array $requestData this array must contain an item with key "name" 
 *                           if you want to display a custom name in the greeting.
 * @return void
 */
$endpoints["sayhello"] = function (array $requestData): void {

    if (!isset($requestData["name"])) {
        $requestData["name"] = "Misterious masked individual";
    }

    echo json_encode("hello! " . $requestData["name"]);
};

/**
 * prints a default message if the endpoint path does not exist.
 * @param array $requestData contains the parameters sent in the request, 
 *                           for this endpoint they are ignored.
 * @return void
 */
$endpoints["404"] = function ($requestData): void {

    echo json_encode("Endpoint " . $requestData["endpointName"] . " not found.");
};

Sama seperti yang Anda saksikan pada bagian kode, kami mendeskripsikan dan simpan fungsi yang bertanggungjawab atas tiap track dalam array $endpoints, ini manfaatkan fungsionalitas "penutupan" (fungsi anonim) dalam PHP. Penutupan ini harus mematuhi perjanjian tanda-tangan fungsi, yang terima array $requestData dan tidak memiliki pengembalian (batal). Dalam array $requestData, parameter yang terterima oleh titik akhir dikirimkan untuk diproses.

Mari teruskan. Saat ini silahkan beralih ke pengkodean respons API kami, dalam masalah ini dan berdasar opsi, yakni, opsi saya, pembaca yang budiman, saya akan memaksa pengkodean dalam pola JSON dan UTF-8 semacam ini:

//we define the encoding of the response, by default we will use json
header("Content-Type: application/json; charset=UTF-8");

Dengan langkah ini semua keluaran data ke browser akan dikodekan dengan karakter JSON dan UTF-8, saya sarankan untuk menghindar kumpulan karakter yang berbeda dan selalu jaga standar, ini bisa secara cepat menjadi hell jika Anda memilih untuk menggunakan type lain.

Seterusnya kita akan menyaksikan di mana keajaiban API terjadi:

if (isset($endpoints[$endpointName])) {
    $endpoints[$endpointName]($requestData);
} else {
    $endpoints["404"](array("endpointName" => $endpointName));
}

Dengan menyimpan titik akhir dalam array, kita dapat memanfaatkan fungsi PHP isset() untuk menentukan apakah track tersebut benar-benar ada atau tidak menggunakan blok if-else yang sangat praktis dan sangat mengurangi kode. Apa yang dilakukan bagian ini adalah memeriksa apakah titik akhir ada dan menjalankannya, jika tidak ada maka secara default akan memanggil titik akhir 404 yang melaporkan bahwa operasi yang Anda coba lakukan tidak ada.

Hingga saat ini, kami memiliki API fungsional tanpa autentikasi, sekarang kami akan mengimplementasikan autentikasi melalui token.

Otentikasi token

Pada bagian ini kita akan mulai dengan mengimplementasikan pendeteksian dan pengumpulan parameter yang dikirim atas permintaan ke endpoint.

Kita dapat mengimplementasikannya dengan cara berikut:

switch ($_SERVER['REQUEST_METHOD']) {
    case 'POST':
        $requestData = $_POST;
        break;
    case 'GET':
        $requestData = $_GET;
        break;
    case 'DELETE':
        $requestData = $_DELETE;
        break;
    case 'PUT':
    case 'PATCH':
        parse_str(file_get_contents('php://input'), $requestData);

        //if the information received cannot be interpreted as an arrangement it is ignored.
        if (!is_array($requestData)) {
            $requestData = array();
        }

        break;
    default:
        //TODO: implement here any other type of request method that may arise.
        break;
}

Kami mendeteksi jenis permintaan dari entri “REQUEST_METHOD” di array PHP $_SERVER. Untuk permintaan POST, GET dan DELETE cukup sederhana, tetapi untuk permintaan PUT dan PATCH Anda perlu mengurai string 'php://input' dan mengubahnya menjadi array menggunakan fungsi parse_str(). Jika di masa mendatang Anda ingin mencakup jenis permintaan lain, Anda dapat melakukannya dengan memperluas cakupan kotak sakelar.

Bagian kode ini akan mencakup penerimaan parameter, termasuk token keamanan yang akan digunakan, jika asal memutuskan untuk memasukkannya ke dalam item yang dikirim melalui GET, POST, PUT, dll. Jika autentikasi menggunakan header digunakan, kami akan membutuhkan kode berikut:

//If the token is sent in a header X-API-KEY
if (isset($_SERVER["HTTP_X_API_KEY"])) {
    $requestData["token"] = $_SERVER["HTTP_X_API_KEY"];
}

API kami akan menggunakan salah satunya header yang umum untuk otentikasi token, yaitu header "X-API-KEY" yang umumnya dipakai oleh AWS dan sudah jadi benar-benar terkenal. Apa yang sudah dilakukan kode kita dalam kasus ini untuk tentukan adakah header X-API-KEY dan simpan token yang diterima dalam array $requestData dengan kunci "token".

Di titik ini kami telah tangkap semua info yang dibutuhkan untuk mengautentikasi permintaan dan mengolah data yang dikirim.

Untuk mengotorisasi atau mungkin tidak operasi berdasar token, kami akan mengaplikasikan penutupan baru dan menambahnya ke dalam daftar titik akhir yang ada. Ini cuma untuk selamatkan kita implementasi secara berlainan ingat ruang cakup contoh.

Penutupan baru (titik akhir) akan terlihat semacam ini:

/**
 * checks if the token is valid, and prevents the execution of 
 * the requested endpoint.
 * @param array $requestData contains the parameters sent in the request, 
 *                           for this endpoint is required an item with 
 *                           key "token" that contains the token
 *                           received to authenticate and authorize 
 *                           the request.
 * @return void
 */
$endpoints["checktoken"] = function ($requestData): void {

    //you can create secure tokens with this line, but that is a discussion for another post..
    //$token = str_replace("=", "", base64_encode(random_bytes(160 / 8)));

    //authorized tokens
    $tokens = array(
        "fa3b2c9c-a96d-48a8-82ad-0cb775dd3e5d" => ""
    );

    if (!isset($requestData["token"])) {
        echo json_encode("No token was received to authorize the operation. Verify the information sent");
        exit;
    }

    if (!isset($tokens[$requestData["token"]])) {
        echo json_encode("The token " . $requestData["token"] . " does not exists or is not authorized to perform this operation.");
        exit;
    }
};

Fungsi ini berisi daftar token yang diotorisasi dalam array $token dan akan memvalidasi apakah token yang diterima ada atau tidak untuk menentukan apakah operasi yang diminta dijalankan atau tidak.

Terakhir, kami akan menerapkan perubahan pada pemanggilan titik akhir kami sehingga setiap kali upaya dilakukan untuk mengeksekusinya, titik akhir validasi token kami akan dijalankan terlebih dahulu, seperti ini:

//we define the response encoding, by default we will use json
header("Content-Type: application/json; charset=UTF-8");

if (isset($endpoints[$endpointName])) {
    // Received token validation.
    $endpoints["checktoken"]($requestData);
    // Endpoint execution.
    $endpoints[$endpointName]($requestData);
} else {
    $endpoints["404"](array("endpointName" => $endpointName));
}

Dan itu akan menjadi akhir dari skrip kami, di bawah ini adalah versi lengkap dari file final:

.htaccess

<Files "*.php">
    Require ip 127.0.0.1
</Files>

<Files "index.php">
    Require all granted
</Files>

RewriteEngine On
#Activate to force https.
#RewriteCond %{HTTPS} off
#RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

RewriteBase /simple-api/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]

index.php

<?php

$BASE_URI = "/simple-api/";
$endpoints = array();
$requestData = array();

//collect incoming parameters
switch ($_SERVER['REQUEST_METHOD']) {
    case 'POST':
        $requestData = $_POST;
        break;
    case 'GET':
        $requestData = $_GET;
        break;
    case 'DELETE':
        $requestData = $_DELETE;
        break;
    case 'PUT':
    case 'PATCH':
        parse_str(file_get_contents('php://input'), $requestData);

        //if the information received cannot be interpreted as an arrangement it is ignored.
        if (!is_array($requestData)) {
            $requestData = array();
        }

        break;
    default:
        //TODO: implement here any other type of request method that may arise.
        break;
}

//If the token is sent in a header X-API-KEY
if (isset($_SERVER["HTTP_X_API_KEY"])) {
    $requestData["token"] = $_SERVER["HTTP_X_API_KEY"];
}

$parsedURI = parse_url($_SERVER["REQUEST_URI"]);
$endpointName = str_replace($BASE_URI, "", $parsedURI["path"]);

if (empty($endpointName)) {
    $endpointName = "/";
}

// closures to define each endpoint logic, 
// I know, this can be improved with some OOP but this is a basic example, 
// don't do this at home, well, or if you want to do it, don't feel judged.

/**
 * prints a default message if the API base path is queried.
 * @param array $requestData contains the parameters sent in the request, for this endpoint they are ignored.
 * @return void
 */
$endpoints["/"] = function (array $requestData): void {

    echo json_encode("Welcome to my API!");
};

/**
 * prints a greeting message with the name specified in the $requestData["name"] item.
 * if the variable is empty a default name is used.
 * @param array $requestData this array must contain an item with key "name" 
 *                           if you want to display a custom name in the greeting.
 * @return void
 */
$endpoints["sayhello"] = function (array $requestData): void {

    if (!isset($requestData["name"])) {
        $requestData["name"] = "Misterious masked individual";
    }

    echo json_encode("hello! " . $requestData["name"]);
};

/**
 * prints a default message if the endpoint path does not exist.
 * @param array $requestData contains the parameters sent in the request, 
 *                           for this endpoint they are ignored.
 * @return void
 */
$endpoints["404"] = function ($requestData): void {

    echo json_encode("Endpoint " . $requestData["endpointName"] . " not found.");
};

/**
 * checks if the token is valid, and prevents the execution of 
 * the requested endpoint.
 * @param array $requestData contains the parameters sent in the request, 
 *                           for this endpoint is required an item with 
 *                           key "token" that contains the token
 *                           received to authenticate and authorize 
 *                           the request.
 * @return void
 */
$endpoints["checktoken"] = function ($requestData): void {

    //you can create secure tokens with this line, but that is a discussion for another post.. 
    //but i am using UUIDv4 instead.
    //$token = str_replace("=", "", base64_encode(random_bytes(160 / 8)));

    //authorized tokens
    $tokens = array(
        "fa3b2c9c-a96d-48a8-82ad-0cb775dd3e5d" => ""
    );

    if (!isset($requestData["token"])) {
        echo json_encode("No token was received to authorize the operation. Verify the information sent");

        exit;
    }

    if (!isset($tokens[$requestData["token"]])) {
        echo json_encode("The token  " . $requestData["token"] . 
        " does not exists or is not authorized to perform this operation.");
        
        exit;
    }
};

//we define the response encoding, by default we will use json
header("Content-Type: application/json; charset=UTF-8");

if (isset($endpoints[$endpointName])) {
    $endpoints["checktoken"]($requestData);
    $endpoints[$endpointName]($requestData);
} else {
    $endpoints["404"](array("endpointName" => $endpointName));
}

Pengujian

Untuk menguji skrip, Anda cukup melakukan permintaan GET dari browser Anda, di lingkungan lokal Anda dapat melakukannya dengan memasukkan URL seperti ini:

http://localhost/simple-api/sayhello?token=fa3b2c9c-a96d-48a8-82ad-0cb775dd3e5d&name=john

Jika Anda ingin menguji menggunakan POST Anda dapat menggunakan curl seperti ini:

curl -d "token=fa3b2c9c-a96d-48a8-82ad-0cb775dd3e5d&name=john" -X POST http://localhost/simple-api/sayhello

Menggunakan tajuk X-API-KEY:

curl -H 'x-api-key: fa3b2c9c-a96d-48a8-82ad-0cb775dd3e5d' -d "name=john" -X POST http://localhost/simple-api/sayhello

Itu dia. Mudah, bukan?

Sekarang Anda memiliki gambaran umum tentang cara kerja API dan cara menerapkannya dengan kemauan keras, keberanian, dan beberapa skrip.