Free tutorials & notes in Hindi & English · Clean code examples 🧰 मुफ़्त PDF और फ़ोटो टूल्स →
🛠️ PHP + MySQL REST API  ·  Lesson 04

Register और Login API (Token)

Token login कैसे काम करता है

App email + password एक बार भेजती है। सही होने पर server एक लंबा random token बनाता है और app उसे (DataStore में) save कर लेती है। उसके बाद हर request header में token लेकर जाती है, और server जान जाता है कि कौन call कर रहा है।

AppPHP APIMySQLPOST auth/login.php {email, password}email से user SELECT → password_verify()INSERT user_tokens (sha256(token), expires_at){token: "c4a45ae7…", user: {…}}token DataStore में saveGET cart/list.php Authorization: Bearer c4a45ae7…sha256(token) से user ढूँढोइस user का cart
एक बार login, फिर हर call के साथ token

Passwords: hash करें, store नहीं

password_hash() password को random salt (bcrypt) के साथ one-way hash में बदलता है। मालिक होकर भी आप password वापस नहीं पढ़ सकते। password_verify() type किए गए password को hash से match करता है।

PHP – हर बार hash अलग बनता है (random salt)
<?php
$hash = password_hash('Test@123', PASSWORD_DEFAULT);
echo $hash, "\n";
var_dump(password_verify('Test@123', $hash));
var_dump(password_verify('test@123', $hash));
$2y$10$uH03CdamJqTh78xX6h8anO2bK5SYInjgY9DUD8F0J54uvIpC.vxje bool(true) bool(false)
🔒 दोनों गलतियों पर एक ही message

Email न हो या password गलत हो — login दोनों में "Wrong email or password." कहता है। अलग message होने पर attackers पता लगा लेंगे कि किन emails के accounts हैं।

Tokens: बनाना और check करना

ये दो helpers (helpers.php में पहले से) token का सारा काम करते हैं। जिस endpoint को logged-in user चाहिए वह शुरुआत में बस $user = require_auth(); call करता है।

PHP – includes/helpers.php (token वाला हिस्सा, extra comments के साथ)
<?php
// From includes/helpers.php

/** Create a new login token for a user. Only its sha256 hash is stored. */
function create_token($userId)
{
    $token = bin2hex(random_bytes(32));          // 32 random bytes = 64 hex characters
    $stmt = db()->prepare('INSERT INTO user_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)');
    $stmt->execute(array($userId, hash('sha256', $token), date('Y-m-d H:i:s', time() + TOKEN_DAYS * 86400)));
    return $token;                                // the app gets the real token ONCE
}

/** Returns the logged-in user row, or stops with 401. */
function require_auth()
{
    $token = bearer_token();                      // reads "Authorization: Bearer <token>"
    if ($token === null) {
        fail('Please login to continue.', 401);
    }
    $stmt = db()->prepare(
        'SELECT u.id, u.name, u.email, u.phone, u.created_at FROM user_tokens t
         JOIN users u ON u.id = t.user_id
         WHERE t.token_hash = ? AND t.expires_at > NOW()'
    );
    $stmt->execute(array(hash('sha256', $token)));
    $user = $stmt->fetch();
    if (!$user) {
        fail('Session expired. Please login again.', 401);
    }
    $user['id'] = (int) $user['id'];
    return $user;
}

App क्या रखती है

  • token = c4a45ae77d…(64 hex)
  • DataStore में saved
  • Authorization header में भेजा जाता है

Database क्या रखता है

  • token_hash = sha256(token)
  • user_id, expires_at (30 दिन)
  • हर logged-in device की एक row
चोरी हुए database से login नहीं हो सकता

Step 1: auth/register.php

सब कुछ server पर validate करें — app भी validate करती है, पर कोई भी API को सीधे call कर सकता है। फिर check करें कि email free है, hashed password के साथ user save करें और तुरंत login कर दें।

PHP – backend/api/auth/register.php
<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
require_fields(array('name' => 'Name', 'email' => 'Email', 'password' => 'Password'));

$name = in_str('name');
$email = strtolower(in_str('email'));
$phone = in_str('phone');
$password = in_str('password');

if (mb_strlen($name) < 2 || mb_strlen($name) > 80) {
    fail('Name must be 2 to 80 characters.', 422);
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    fail('Please enter a valid email.', 422);
}
if ($phone !== '' && !preg_match('/^[6-9][0-9]{9}$/', $phone)) {
    fail('Please enter a valid 10-digit mobile number.', 422);
}
if (strlen($password) < 6) {
    fail('Password must be at least 6 characters.', 422);
}

$stmt = db()->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute(array($email));
if ($stmt->fetch()) {
    fail('This email is already registered. Please login.', 409);
}

$stmt = db()->prepare('INSERT INTO users (name, email, phone, password_hash) VALUES (?, ?, ?, ?)');
$stmt->execute(array($name, $email, $phone === '' ? null : $phone, password_hash($password, PASSWORD_DEFAULT)));
$userId = (int) db()->lastInsertId();

ok(array(
    'token' => create_token($userId),
    'user'  => array('id' => $userId, 'name' => $name, 'email' => $email, 'phone' => $phone === '' ? null : $phone),
), 'Account created successfully.');
सिर्फ POSTrequire_methodज़रूरी fieldsname, email, passwordValidatename length, email, Indian mobile, password ≥ 6Email free?पहले से हो तो 409INSERT userpassword_hash()Token + user लौटाएँapp logged in
register.php ऊपर से नीचे

Step 2: auth/login.php

PHP – backend/api/auth/login.php
<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
require_fields(array('email' => 'Email', 'password' => 'Password'));

$email = strtolower(in_str('email'));
$password = in_str('password');

$stmt = db()->prepare('SELECT id, name, email, phone, password_hash FROM users WHERE email = ?');
$stmt->execute(array($email));
$user = $stmt->fetch();

// Same message for "no such email" and "wrong password": do not reveal which emails exist.
if (!$user || !password_verify($password, $user['password_hash'])) {
    fail('Wrong email or password.', 401);
}

// Remove this user's expired tokens while we are here.
db()->prepare('DELETE FROM user_tokens WHERE user_id = ? AND expires_at < NOW()')->execute(array($user['id']));

ok(array(
    'token' => create_token((int) $user['id']),
    'user'  => array('id' => (int) $user['id'], 'name' => $user['name'], 'email' => $user['email'], 'phone' => $user['phone']),
), 'Welcome back, ' . $user['name'] . '!');

Step 3: me, update, logout

me.php profile के साथ तीन counters (orders, wishlist, cart) लौटाता है जो Account screen दिखाती है — sub-selects वाली एक ही query में।

PHP – backend/api/auth/me.php
<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_method('GET');
$user = require_auth();

$counts = db()->prepare(
    'SELECT
       (SELECT COUNT(*) FROM orders WHERE user_id = ?) AS orders,
       (SELECT COUNT(*) FROM wishlist WHERE user_id = ?) AS wishlist,
       (SELECT COALESCE(SUM(qty), 0) FROM cart_items WHERE user_id = ?) AS cart'
);
$counts->execute(array($user['id'], $user['id'], $user['id']));
$c = $counts->fetch();

$user['order_count'] = (int) $c['orders'];
$user['wishlist_count'] = (int) $c['wishlist'];
$user['cart_count'] = (int) $c['cart'];
ok($user);

update.php name और phone बदलता है, और password सिर्फ तब जब current password सही हो:

PHP – backend/api/auth/update.php
<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
$user = require_auth();
require_fields(array('name' => 'Name'));

$name = in_str('name');
$phone = in_str('phone');
if (mb_strlen($name) < 2 || mb_strlen($name) > 80) {
    fail('Name must be 2 to 80 characters.', 422);
}
if ($phone !== '' && !preg_match('/^[6-9][0-9]{9}$/', $phone)) {
    fail('Please enter a valid 10-digit mobile number.', 422);
}
db()->prepare('UPDATE users SET name = ?, phone = ? WHERE id = ?')
    ->execute(array($name, $phone === '' ? null : $phone, $user['id']));

// Optional password change: needs the current password.
$newPass = in_str('new_password');
if ($newPass !== '') {
    $stmt = db()->prepare('SELECT password_hash FROM users WHERE id = ?');
    $stmt->execute(array($user['id']));
    if (!password_verify(in_str('current_password'), $stmt->fetchColumn())) {
        fail('Current password is wrong.', 422);
    }
    if (strlen($newPass) < 6) {
        fail('New password must be at least 6 characters.', 422);
    }
    db()->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
        ->execute(array(password_hash($newPass, PASSWORD_DEFAULT), $user['id']));
}

ok(array('id' => $user['id'], 'name' => $name, 'email' => $user['email'], 'phone' => $phone === '' ? null : $phone), 'Profile updated.');

logout.php इस device का token delete करता है। बाकी devices logged in रहते हैं।

PHP – backend/api/auth/logout.php
<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
require_auth();

db()->prepare('DELETE FROM user_tokens WHERE token_hash = ?')->execute(array(hash('sha256', bearer_token())));
ok(null, 'Logged out.');

Step 4: curl से test

Terminal
# Login (JSON body)
curl -X POST http://localhost:8000/api/auth/login.php \
  -H "Content-Type: application/json" \
  -d '{"email":"test@karteasy.in","password":"Test@123"}'

# Copy the token from the answer, then call a protected endpoint
TOKEN=paste-the-64-character-token-here
curl http://localhost:8000/api/auth/me.php -H "Authorization: Bearer $TOKEN"

# Logout
curl -X POST http://localhost:8000/api/auth/logout.php -H "Authorization: Bearer $TOKEN"

ये KartEasy API के असली जवाब हैं:

Login – success
POST /karteasy/api/auth/login.php {"email": "test@karteasy.in", "password": "Test@123"} HTTP 200 { "success": true, "message": "Welcome back, Riya Sharma!", "data": { "token": "d626105b32…(64 hex chars)", "user": { "id": 1, "name": "Riya Sharma", "email": "test@karteasy.in", "phone": "9876543210" } } }
Login – गलत password
POST /karteasy/api/auth/login.php {"email": "test@karteasy.in", "password": "wrong"} HTTP 401 { "success": false, "message": "Wrong email or password.", "data": null }
Register – email पहले से use
POST /karteasy/api/auth/register.php {"name": "Riya", "email": "test@karteasy.in", "password": "secret1"} HTTP 409 { "success": false, "message": "This email is already registered. Please login.", "data": null }
Register – गलत email
POST /karteasy/api/auth/register.php {"name": "Aman", "email": "aman@x", "password": "abc123"} HTTP 422 { "success": false, "message": "Please enter a valid email.", "data": null }
Token के साथ profile
GET /karteasy/api/auth/me.php Authorization: Bearer 3f9c…e1a7 HTTP 200 { "success": true, "message": "OK", "data": { "id": 1, "name": "Riya Sharma", "email": "test@karteasy.in", "phone": "9876543210", "created_at": "2026-10-19 01:28:55", "order_count": 0, "wishlist_count": 0, "cart_count": 0 } }
बिना token protected endpoint
GET /karteasy/api/cart/list.php HTTP 401 { "success": false, "message": "Please login to continue.", "data": null }
Logout, फिर वही token दोबारा
POST /karteasy/api/auth/logout.php Authorization: Bearer 3f9c…e1a7 {} HTTP 200 { "success": true, "message": "Logged out.", "data": null } GET /karteasy/api/auth/me.php Authorization: Bearer 3f9c…e1a7 HTTP 401 { "success": false, "message": "Session expired. Please login again.", "data": null }

आम गलतियाँ

  • Hosting पर Authorization header गायब → backend/.htaccess का RewriteRule रखें।
  • Passwords को == से compare करना → हमेशा password_verify()।
  • JSON में password_hash लौटाना → सिर्फ ज़रूरी columns select करें।
  • Tokens के लिए rand() या uniqid() → guess हो सकते हैं; random_bytes() use करें।
  • Tokens कभी expire न होना → हम 30 दिन रखते हैं और login पर पुराने साफ़ करते हैं।

अक्सर पूछे जाने वाले प्रश्न (FAQ)

App के लिए PHP sessions क्यों नहीं?
Sessions cookies पर चलते हैं, जिन्हें mobile apps अपने आप नहीं संभालतीं। Authorization header में token simple है, किसी भी client से चलता है, और server एक row delete करके उसे रद्द कर सकता है।
Token का सिर्फ SHA-256 क्यों store करते हैं?
अगर कभी database backup चोरी हो जाए तो चोर को hashes मिलेंगे, चलने वाले tokens नहीं। Password hashing जैसा ही idea है, पर tokens लंबी random strings हैं इसलिए यहाँ SHA-256 काफ़ी है।
क्या यह JWT है?
नहीं। यह random (opaque) token है जो database से check होता है। JWT से समझना और रद्द करना आसान है, और shop app के लिए काफ़ी तेज़ है।
← E-commerce App Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

अगला topicProducts API: Search, Filter, Pages आपकी प्रगति: 4 / 28 (14%) · Lesson 5 / 28 आगे बढ़ें →

यह lesson कैसा लगा?

आपकी राय से हम content और बेहतर बनाते हैं।

अपना review और सुझाव लिखें (वैकल्पिक)

Review approve होने के बाद आपका नाम और शहर इसके साथ public दिख सकते हैं। Email या phone न लिखें।

▶ CodingEasily on YouTube

Video से सीखें – आसान Hindi में

चुनी हुई video: RDBMS भाग 3 – Class XI Commerce IT

▶ Channel subscribe करें