Чат PHP-разработчиков
Логические задачи с собеседований
Как сделать авторизацию на сайте на PHP

Система авторизации с помощью cookie на PHP

В прошлом уроке мы изучили механизм взаимодействия с cookie в языке PHP. Теперь давайте попробуем применить эти знания на практике — создадим простейшую систему авторизации.

Техническое задание

Начнём мы это дело с описания будущей системы. Пусть у нас будут следующие компоненты:

  1. Главная страница сайта с каким-либо содержимым. Вверху страницы выводится:
    • если пользователь авторизован: Добро пожаловать, %username%.
    • если пользователь неавторизован: Авторизация — слово является ссылкой, которая ведёт на форму авторизации.
      Авторизован пользователь или нет, определяется с помощью cookie.
  2. Страница с формой авторизации. Два инпута для логина и пароля и кнопкой «Вход». Если введены правильные логин и пароль, устанавливаются cookie со значениями переданных данных, а затем пользователя автоматически редиректит (перенаправляет) на главную страницу.
  3. Страница для разлогинивания — при переходе на неё cookie будут удаляться из браузера пользователя, а затем выполняется редирект на главную страницу.

Решение

Продумываем архитектуру

Первое, о чём нам нужно подумать — это то, как будут храниться элементы этой системы, и сколько их вообще будет.

Начнем с простого — для начала у нас должно получиться 3 странички, которые мы описали в ТЗ.

Ещё нам потребуется функционал, который будет проверять, авторизован ли пользователь. Если мы перечитаем ТЗ, то поймём, что он используется в двух местах — и на главной странице и на странице авторизации. Значит, стоит вынести этот механизм в отдельный файл, и использовать его в двух местах сразу.

Ну и наконец, нам где-то нужно хранить самих пользователей, а именно — их логины и пароли. Создадим для этого простенькую «базу данных» - массив, с набором пар логин-пароль. Это ещё один файл.

Пишем код

Все исходники по данному заданию доступны здесь.

База данных

Ну вот, всё продумали, осталось только написать. Предлагаю начать с нашей базы данных. Создадим файл usersDB.php и запишем в него несколько пользователей.

Код доступен только после покупки курса PHP для начинающих.

Функции проверки авторизации

Давайте теперь напишем функцию, которая будет проверять, являются ли переданные в неё логин и пароль правильными. Для этого создадим ещё один файл auth.php. В нём нам для получения списка пользователей потребуется подключить файл с базой данных.

Код доступен только после покупки курса PHP для начинающих.

В цикле мы пробегаемся по базе данных пользователей и пытаемся найти пользователя с переданными логином и паролем. Если такой пользователь в массиве найден — возвращаем true. Иначе — false.

Давайте теперь ещё напишем функцию, которая будет возвращать логин текущего пользователя. Эта функция будет проверять текущие значения cookie с ключами login и password с помощью уже существующей функции checkAuth. При этом если пользователь найдётся, то она вернёт его login, а иначе — null. Назовём эту нашу новую функцию getUserLogin.

Код доступен только после покупки курса PHP для начинающих.

На этом всю логику проверки логина мы описали. Теперь займёмся непосредственно страничками.

Главная страница

Создадим файл index.php. Для простоты примера мы будем использовать только строку с приветствием авторизованного пользователя, либо ссылкой на авторизацию. В этой странице нам потребуется функция проверки авторизации через cookie, поэтому здесь нужно подключить файл auth.php.

Код доступен только после покупки курса PHP для начинающих.

Наша главная страничка готова. Можно зайти на неё и убедиться, что мы не авторизованы.

Главная страничка с призывом авторизации

Форма авторизации

Давайте теперь сделаем форму авторизации — создаём файл login.php и для начала набрасываем саму HTML-форму. Шаблон получился следующим.

Код доступен только после покупки курса PHP для начинающих.

Давайте теперь добавим логику проверки переданных данных.

Код доступен только после покупки курса PHP для начинающих.

Логика простейшая — если был отправлен POST-запрос, проверяем правильные ли логин и пароль были переданы.

Если нет — то создаём переменную $error, в которой пишем об ошибке авторизации. Позже в шаблоне выводим эту ошибку, если эта переменная объявлена.

Если же авторизация прошла успешно, мы устанавливаем cookie с ключами login и password, в которые помещаем значения из POST-запроса. После этого выполняем редирект на главную страницу.

Редирект делается с помощью заголовка в HTTP-ответе. Этот заголовок называется Location и выглядит следующим образом:

Код доступен только после покупки курса PHP для начинающих.

Для формирования заголовков в PHP используется функция header – ознакомиться с ней более детально вы можете здесь.

Теперь можно попробовать нашу страничку в действии. Давайте для начала введём несуществующую пару логина и пароля. Например, 123:123.

Ошибка при авторизации

Мы увидим соответствующую ошибку.

Теперь давайте зайдем под пользователем user. В нашей БД для него указан пароль 123. Пробуем...

Успешная авторизация

Успех! Нас автоматически перекинуло на главную страницу, где мы видим приветствие для данного пользователя!

Безопасная система авторизации

Однако, данная схема имеет недостаток - пароль используется в открытом виде, а это небезопасно. Всё что идёт дальше - только для ознакомления, пока это слишком сложно реализовать без хорошей архитектуры и полноценной базы данных.

Хеширование паролей

В более совершенных системах авторизации используют хеш от пароля.

Если по-простому, то это такое вычисленное значение, полученное в результате выполнения над паролем определенных манипуляций. В результате этих действий мы получаем строку, из которой нельзя восстановить исходный пароль.

Но мы можем снова повторить над паролем те же действия и сравнить получившиеся значения. То есть сравниваются хеши, а не исходные пароли. И в базе данных тоже хранят хеши, а после того как от клиента пришел пароль в открытом виде, вычисляют его хэш и сравнивают со значением в базе. Если они равны - значит от пользователя пришел верный пароль.

Для чего это делается? Да просто потому, что если сайт будет каким-то образом взломан, то злоумышленник в базе данных не найдёт паролей в открытом виде - только хеши. А так как из хеша получить пароль довольно сложно (при условии, что хеш-функция надежна и используется надёжный пароль), то пароль он не узнает. Следовательно:

  1. злоумышленник не сможет использовать пароль для входа на взломанный сайт;
  2. он также не сможет использовать этот пароль для входа под тем же логином и паролем в другие места (ведь довольно часто люди используют одинаковые пароли для всего).

Вычисляются хеши с помощью хеш-функции. Хеш-функции при этом вычисляют хеши следуя алгоритмам хеширования. Сейчас в PHP для хеширования следует использовать функцию password_hash(), а для проверки хеша - password_verify(). Если вы в каком-то уроке увидите, что для хеширования паролей используется md5 - бегите оттуда, такие хеши вскрываются за несколько минут, она устарела ещё лет 10 назад.

Авторизационные токены

Помимо хеша пароля в базе данных так же принято хранить так называемые авторизационные токены (AuthToken). Это комбинация символов (желательно подлиннее и с кучей кракозябр), которая генерируется при успешной авторизации пользователя и сохраняется в базе данных. А ещё она и пользователю отправляется.

И потом пользователь с помощью cookie передает этот токен на сервер, где он сравнивается со значением в базе данных. Если они равны, то считаем пользователя авторизованным. Для чего? Дело в том, что куки могут быть похищены злоумышленниками (очень многими способами, не будем об этом в этой статье, кому интересно - погуглите). И если злоумышленнику попадет в руки токен - он не сможет получить исходный пароль. О том, почему это так важно, я уже объяснил.

Заключение

Повторюсь, два последних параграфа здесь только для ознакомления. Реализуем мы их в дальнейших уроках, когда будем знать чуть больше вещей. Пока что - вот такая простейшая но небезопасная система авторизации.

Ах да, чуть не забыл, все исходники к каждому уроку я для вашего удобства буду выкладывать на github – вот тут.

Читайте также
Комментарии


fantasyz
fantasyz

удаление

if (!empty($_COOKIE)) {
    setcookie('login', $login, -10, '/');
    setcookie('password', $password, -10, '/');
    header('Location: /cookie/index.php');
}

проверка

if (!empty($_COOKIE)) {
    header('Location: /cookie/index.php');
}
ivashkevich
ivashkevich

Хорошо, всё норм. Только для удаления cookie можно в качестве значения вообще передать пустую строку:

setcookie('login', '', -10, '/');
ivashkevich
ivashkevich

А нет, не норм. Проверка проверяет только наличие cookie. При этом не гарантируется, что пользователь авторизован.

fantasyz
fantasyz
if (!empty($_COOKIE['login'] && $_COOKIE['password'])) {
    header('Location: /cookie/index.php');
} 

вот так по идее гарантируется

ivashkevich
ivashkevich

Нет, так гарантируется только то, что в cookie есть какие-то данные, но не гарантируется, что эти данные принадлежат какому-то пользователю.

В общем, нужно попытаться их использовать для логина, и если логин успешен, то тогда уже редиректить. Иначе - считать пользователя неавторизованным.

fantasyz
fantasyz
require __DIR__ . '/auth.php';

if (!empty($_COOKIE)) {
    $login = $_COOKIE['login'];
    $password = $_COOKIE['password'];
    if (checkAuth($login, $password)) {
        header('Location: /cookie/index.php');
    }
}
ivashkevich
ivashkevich

Вот, теперь логика правильная. Единственное что не учли - в cookie может что-то лежать, но не будет ключей login и password. В таком случае будет warning.

Нужно предусмотреть такой кейс и в переменную login положить пустую строку, если $_COOKIE['login'] не существует:

$login = $_COOKIE['login'] ?? '';

С паролем аналогично.

fantasyz
fantasyz

понял, с горем пополам осилил дз)), точнее с вашей помощью), спасибо большое!

ivashkevich
ivashkevich

Не за что. Главное, что вы пробуете. Это 99% успеха :)

1nSide
1nSide

logout.php

<?php

setcookie('login', '$login', -10, '/');
setcookie('password' , '$password', -10, '/');
header('location: indexCookie.php' );
1nSide
1nSide

Во втором задании уже была проверка

ivashkevich
ivashkevich

При простом переходе (без отправки POST-запроса) не будет у вас никакой проверки. Задание требует выполнения проверки при любом запросе.

1nSide
1nSide

а где пароли хешировать? это если пользователь вошел то сделать чтобы они хешировались?

ivashkevich
ivashkevich

Знаете, я переборщил с этим заданием. С последними двумя параграфами пока только ознакомьтесь (я их только что подправил). Усовершенствуем систему чуть позже.

arminbuuren86@gmail.com
arminbuuren86@gmail.com
function getUserLogin(): ?string
{
    $loginFromCookie = $_COOKIE['login'] ?? '';
    $passwordFromCookie = $_COOKIE['password'] ?? '';

    if (checkAuth($loginFromCookie, $passwordFromCookie)) {
        return $loginFromCookie;
    }

    return null;
}

Пишет, что ошибка в ?string на знак ? указывает, почему?

ivashkevich
ivashkevich

Версия PHP 7.1? Если ниже - то не заработает.

arminbuuren86@gmail.com
arminbuuren86@gmail.com

CTRL + ALT + S -> Languages & Frameworks -> PHP. Там выбрана 7.1, еще есть ниже версии и выше 7.2. Может 7.2 выбрать?

ivashkevich
ivashkevich

PhpStorm - это только редактор. Ошибка возникает во время выполнения скрипта, значит менять нужно версию интерпретатора. Если Вы всё делаете по урокам, то версию нужно менять в настройках OpenServer. Повторите уроки как работает PHP и установка и настройка OpenServer.

arminbuuren86@gmail.com
arminbuuren86@gmail.com

Спасибо за ответ. Буду разбираться. И спасибо вам за ваши уроки! Отличные и реально быстро понимаешь, что да как)

ivashkevich
ivashkevich

Спасибо за отзыв) Удачи в прохождении)

arminbuuren86@gmail.com
arminbuuren86@gmail.com
Parse error: syntax error, unexpected '?' in C:\OpenServer\OSPanel\domains\autorizacion.loc\www\auth.php on line 15

вот что пишет

Fox-24
Fox-24

Спасибо за еще один урок!)

<?php

// Создайте страницу для разлогинивания - logout.php.
// При переходе на неё должны удаляться cookie с ключами login и password и выполняться редирект на главную страницу.
// В качестве ответа предоставьте полный код файла logout.php.

if (!empty($_COOKIE['login'])) {
    // Удаляем Cookie
    setcookie('login', $login, -10, '/');
    setcookie('password', $login, -10, '/');
    header('Location: /autorization/index.php');
}

У меня php 5.4, так что..)

//Сейчас при переходе авторизованного пользователя на страницу login.php открывается форма авторизации.
//Сделайте предварительную проверку того, что пользователь уже авторизован. И если он является авторизованным, перенаправляйте его на главную страницу.
// В качестве ответа на это задание предоставьте только код, который вы добавили в файл login.php.

if (!empty($_COOKIE)) {
    require __DIR__ . '/auth.php';
    if (isset($_COOKIE['login'])) {
        $login = $_COOKIE['login'];
    } else {
        // присваиваем $login значение '' если $_COOKIE['login'] равен NULL
        $login = '';
    }
    if (isset($_COOKIE['password'])) {
        $password = $_COOKIE['password'];
    } else {
        // присваиваем $password значение '' если $_COOKIE['password'] равен NULL
        $password = '';
    }

    $password = $_COOKIE['password'];
    if (checkAuth($login, $password)) {
        header('Location: /autorization/index.php');
    }
}
ivashkevich
ivashkevich
  1. Можно не проверять перед удалением кукисов:
    if (!empty($_COOKIE['login'])) {
  2. Вот эта строка может накидать warning-ов (которая вне условия):
    $password = $_COOKIE['password'];
Fox-24
Fox-24
  1. Да, спасибо:)
  2. Забыл почистить код..

    require __DIR__ . '/auth.php';
    if (isset($_COOKIE['login'])) {
        $login = $_COOKIE['login'];
    } else {
        // присваиваем $login значение '' если $_COOKIE['login'] равен NULL
        $login = '';
    }
    if (isset($_COOKIE['password'])) {
        $password = $_COOKIE['password'];
    } else {
        // присваиваем $password значение '' если $_COOKIE['password'] равен NULL
        $password = '';
    }

    if (checkAuth($login, $password)) {
        header('Location: /autorization/index.php');
    }
dom1r
dom1r

В комментариях уже прочитал, что проверку куки можно опустить и в принципе unset тоже, но:

<?php
//logout.php
if (isset($_COOKIE['login'])) {
    unset($_COOKIE['login']);
    unset($_COOKIE['password']);
    setcookie('login', null, -1, '/');
    setcookie('password', null, -1, '/');
    header('Location: /index.php');
} else {
    return false;
}

Сначала просто проверял isset-ом далее редиректил, в комментариях вышел прочел, подправил:

//login.php
if (isset($_COOKIE['login']) && isset($_COOKIE['password'])) {
    require __DIR__ . '/auth.php';
    $login = $_COOKIE['login'];
    $password = $_COOKIE['password'];

    if (checkAuth($login, $password)) {
        header('Location: /index.php');
    }

}

Также не понятные языковые конструкции вроде этих:

function getUserLogin(): ?string

Понятно что возвращаемое значение должно быть строкой, но не понятен вопросительный знак.

Также и здесь:

$loginFromCookie = $_COOKIE['login'] ?? '';
ivashkevich
ivashkevich

Вот это бессмысленно:

    unset($_COOKIE['login']);
    unset($_COOKIE['password']);

Вопросительный знак означает, что помимо строки может вернуться null.

function getUserLogin(): ?string

?? - null coalescing operator. Если перед ним null - то вернет значение из правой части, иначе - значение слева.

$loginFromCookie = $_COOKIE['login'] ?? '';
RusTaylor
RusTaylor
if (!empty($_COOKIE['login'] && !empty($_COOKIE['password']))){
        $login = $_COOKIE['login'];
        $password = $_COOKIE['password'];
        if (checkAuth($login,$password)){
            header('Location:/index.php');
        }
    }

У меня вот такая проверка куки получилась,только при переходе в login.php всё равно требует вводить пароль, хотя куки есть

ivashkevich
ivashkevich

Проверьте дебаггером, где ошибка.

RusTaylor
RusTaylor

В панели разработчика пишет что не даёт куки файлам вставить пароль и логин

ivashkevich
ivashkevich

Напиши в личку в ВК или Телеге.

ArtemijeKA
ArtemijeKA
  1. logout.php

    setcookie('login', '', -1, '/');
    setcookie('password', '', -1, '/');
    /**
    * У меня папка этого задания называется 23 в локальном сервере webshake, то есть webshake/23
    * по этому я сначала ставлю / после logout.php а потом поднимаюсь до дирректории повыше ../ и перехожу в index.php
    */
    header("Location: " . $_SERVER['REQUEST_URI'] . "/../index.php");
  2. В начале login.php добавил:

    require __DIR__ . '/auth.php';
    if (isset($_COOKIE['login']) && isset($_COOKIE['password'])) {
        if (checkAuth($_COOKIE['login'], $_COOKIE['password'])) {
            header('Location: ' . $_SERVER['REQUEST_URI'] . '/../index.php');
        }
    }

Сначала переписал на require_once потом понял, что там еще подключается usersDB и если там данные изменятся то не получится авторизоваться и исправил этот косяк за собой.

ivashkevich
ivashkevich

Можно просто Location: /index.php
И для первого и для второго случаев. А так - норм.

ArtemijeKA
ArtemijeKA

Я забыл написать у меня локальный сервер webshake а папка с заданием 23 и того webshake/23/ по этому если я напишу "Location: /index.php" то перекинет на webshake.

Benya
Benya
<?php
setcookie('login', $login, time()-3600, '/');
setcookie('password', $password, time()-3600, '/');
header('Location: /index.php');
Benya
Benya
<?php
require __DIR__ . '/auth.php';
$login = getUserLogin();

if  ($login === null) {
    if (!empty($_POST)) {

        $login = $_POST['login'] ?? '';
        $password = $_POST['password'] ?? '';

        if (checkAuth($login, $password)) {
            setcookie('login', $login, 0, '/');
            setcookie('password', $password, 0, '/');
            header('Location: /index.php');
        } else {
            $error = 'Ошибка авторизации';
        }

    }
}else {
    header('Location: /index.php');
}
?>
SBTesla
SBTesla
<?php
//проверяем переданны ли данные и подключаем файл авторизации
if (!empty($_POST)) {
     require __DIR__ . '/auth.php';

     //заносим переданые данные в переменные
     $login = $_COOKIE['login'] ?? '';
     $password = $_COOKIE['password'] ?? '';

     /*проверяем попали ли данные в куки и какие, устанавливаем
     время жизни и директорию*/
     if (checkAUth($login, $password)) {
          setcookie('login', $login, 0, '/');
          setcookie('password', $password, 0, '/');
          header('location: index.php');
     } else {
          $error = 'Ошибка Авторизации';
     }

}

?>

2

<?php
/* удаление cookie , устанавливаем время 
и направляем пользователя на главную страницу
*/
setcookie('login', $login, time() -3600, '/');
setcookie('password', $password, time()-3600, '/');
header('location: /index.php');
ivashkevich
ivashkevich
  1. нужно проверять не $_POST а $_COOKIE
  2. 100 секунд может не хватить, лучше сразу на час назад отбрасывать, если у пользователя косяки со временем
SBTesla
SBTesla

Ага сори поправил,а 100 секунд посоветовали на стаке поставить сказали что ставить на час это костыли, вобще в идеале говорили про -1, но я не совсем понял про -1

ivashkevich
ivashkevich

Перечитайте домашку и посмотрите что вы делаете - проверяете наличие куки, а потом данные POST-запроса - это не имеет смысла.

antoxa
antoxa

Добрый день! Спасибо за урок, возник вопрос, ответь пожалуйста с точки зрения максимальной безопасности.
Сгенерировали auth_token, записали его в базу при входе на сайт и в куки к пользователю.
Дальше при повторном посещении сайта пользователем, сравниваем этот токен и если он совпадает с тем что в базе, логинем пользователя.
Допустим этот токен крадут, вставляют себе в куки и посещают сайт. Происходит логин уже поддельного пользователя.
Как должен выглядеть алгоритм правильно и безопасно чтоб даже если украдут куки не смогли войти?

ivashkevich
ivashkevich

Самый простой вариант - сохранять в базу еще и IP, для которого этот токен создавался. Но есть и более изощренные способы защиты. Стоит устанавливать на сайте SSL-сертификат и cookie передавать только по зашифрованному каналу. Еще стоит запретить куки для JS - всё это можно сделать с помощью setcookie - почитайте документацию по этой функции.

antoxa
antoxa

Спасибо за ответ.
Проверять IP будет плохо для пользователя, он может меняться.
Целый день гуглил сегодня)
В итоге вот к чему пришел: При логине через логин пароль, генерируем токен и пишем в куки и в базу, так же пишем user agent в базу.
При повторном заходе смотрим есть ли токен в куках, если есть, ищем такой в базе, если находим, то сравниваем user agent если все совпадает, то логинем пользователя, ставим переменную в сессию и обновляем токен.
++ как Вы сказали SSL и httponly в true.

Поправьте пожалуйста если что не так)

ivashkevich
ivashkevich

В целом - лучше. Но вообще - если будут красть куки, то подделать UserAgent - вообще не проблема. Потому что куки всегда крадутся через какой-нибудь сторонний ресурс, получить на этом ресурсе юзерагент - вообще не проблема. Продвинутые защищенные системы могут строиться на совокупности сразу нескольких характеристик - UserAgent, расширение экрана, язык и версия системы, IP, и еще куча всего.

antoxa
antoxa

Тут смысл в том что при логине пользователя токен поменяется.
И куками можно воспользоваться пока токен не поменялся.
Плюс на сколько я понял при использовании SSL куки можно украсть только в зашифрованном виде, верно?

ivashkevich
ivashkevich

Нет. Украсть куки можно не только перехватом трафика между пользователем и сайтом. Есть XSS атаки. Если интересуетесь темой безопасности, можете прочитать вот эту вводную статью =)

lordbear53@gmail.com
lordbear53@gmail.com

1)

if(!empty($_COOKIE)) {
    setcookie('login', $login, 1, '/');
    setcookie('password', $password, 1, '/');
    header('Location: login.php');
}

2)

if(!empty($_COOKIE)){
    header('Location: index.php');
}
ivashkevich
ivashkevich
  1. Что будет, если я зайду на страничку логаута, не будучи залогиненным?
  2. Наличие куки еще не значит, что пользователь авторизован! Нужна нормальная проверка того, что в куки пришло.
demyanovpaul@yandex.ru
demyanovpaul@yandex.ru

Создайте страницу для разлогинивания - logout.php. При переходе на неё должны удаляться cookie с ключами login и password и выполняться редирект на главную страницу. В качестве ответа предоставьте полный код файла logout.php.

<?php
setcookie('login','0',time() - 3600);
setcookie('password','0',time() - 3600);
header('Location: index.php');

Сейчас при переходе авторизованного пользователя на страницу login.php открывается форма авторизации. Сделайте предварительную проверку того, что пользователь уже авторизован. И если он является авторизованным, перенаправляйте его на главную страницу. В качестве ответа на это задание предоставьте только код, который вы добавили в файл login.php.

<?php
//Задание 2
if(!empty($_COOKIE['login']) && !empty($_COOKIE['password'])){
    header('Location: index.php');
}

if (!empty($_POST)) {
    require __DIR__ . '/auth.php';

    $login = $_POST['login'] ?? '';
    $password = $_POST['password'] ?? '';

    if (checkAuth($login, $password)) {
        setcookie('login', $login, 0, '/');
        setcookie('password', $password, 0, '/');
        header('Location: /index.php');
    } else {
        $error = 'Ошибка авторизации';
    }
}
?>
<html>
<head>
    <title>Форма авторизации</title>
</head>
<body>
<?php if (isset($error)): ?>
    <span style="color: red;">
    <?= $error ?>
</span>
<?php endif; ?>
<form action="/login.php" method="post">
    <label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
    <br>
    <label for="password">Пароль: </label><input type="password" name="password" id="password">
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>
ivashkevich
ivashkevich

Для заголовка Location лучше указывать абсолютный путь:

Location: /index.php

Так точно перенесет на index.php в корне сайта.

if(!empty($_COOKIE['login']) && !empty($_COOKIE['password'])){
    header('Location: index.php');
}

Наличие Cookie в браузере ещё не означает, что пользователь авторизован. Если они есть, нужно их еще проверить.

demyanovpaul@yandex.ru
demyanovpaul@yandex.ru
if(!empty($_COOKIE['login']) && !empty($_COOKIE['password'])){
    if(checkAuth($_COOKIE['login'], $_COOKIE['password'])){
        header('Location: /index.php');
    }
}

Спасибо, поправил.

ivashkevich
ivashkevich

Хорошо. После отправки header-а location можно сразу завершить работу скрипта с помощью die().