Авторизация без пароля в Laravel

Если вы когда-либо пользовались такими сайтами, как Vercel или Medium, вы, вероятно, раньше сталкивались с входом без пароля.

Обычно процесс выглядит следующим образом: -> введите свой адрес электронной почты -> форма отправки -> письмо будет отправлено вам -> вы щелкните ссылку внутри -> вы вошли в систему.

Это довольно удобный способ для всех. Пользователям не нужно запоминать пароль с произвольным набором правил веб-сайта, а веб-мастерам (люди все еще используют этот термин?) не нужно беспокоиться об утечках паролей или о том, достаточно они безопасны.

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

Мы предполагаем, что у вас есть рабочее понимание структуры MVC Laravel и что в вашей среде уже настроены Composer и PHP.

Обратите внимание, что кодовые блоки в этой статье могут не включать весь файл для краткости.

Настройка окружения

Начнем с создания нового приложения Laravel 8:

$ composer create-project laravel/laravel magic-links

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

В моем случае я использую PostgreSQL. Откройте файл .env:

# .env
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=magic_link
DB_USERNAME=postgres
DB_PASSWORD=postgres

Теперь наша база данных настроена, но пока не запускайте миграции! Давайте посмотрим на миграцию пользователей по умолчанию, которую Laravel создал для нас в database/migrations/2014_10_12_000000_create_users_table.php.

Вы увидите, что таблица пользователей по умолчанию содержит столбец для пароля. Поскольку мы выполняем аутентификацию без пароля, мы можем избавиться от нее:

public function up()
{
  Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->rememberToken();
    $table->timestamps();
  });
}

Cохраните файл после удаления этой строки. Также удалим миграцию для таблицы сброса паролей, поскольку она нам не пригодится:

$ rm database/migrations/2014_10_12_100000_create_password_resets_table.php

Наша исходная схема базы данных готова, поэтому давайте запустим наши миграции:

$ php artisan migrate

Давайте также удалим атрибут пароля из массива $fillable модели пользователя в app/Models/User.php, поскольку он больше не существует:

protected $fillable = [
  'name',
  'email',
];

Нам также нужно настроить наш почтовый драйвер, чтобы мы могли предварительно просматривать наши электронные письма для входа в систему. В целях обучения можно попробовать сервис Mailtrap, который является бесплатным инструметом для перехвата SMTP-трафика (вы можете отправлять электронные письма на любой адрес, и они будут отображаться только в Mailtrap, а не доставляться фактическому пользователю), но вы можете использовать все, что захотите.

Если вы не хотите ничего настраивать, вы можете использовать журнал отправки, и письма будут отображаться в storage/logs/laravel.log в виде необработанного текста.

Вернемся в тот же файл .env:

# .env
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=redacted
MAIL_PASSWORD=redacted
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=hello@example.com

Теперь мы готовы приступить к разработке!

Что предстоит сделать

В начале статьи мы говорили о том, как выглядит кейс с точки зрения пользователя, но как это работает с технической точки зрения?

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

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

Поскольку нам нужно отслеживать, был ли уже использован токен, мы собираемся сохранить их в базе данных. Также будет удобно отслеживать, какой токен принадлежит какому пользователю, а также, был ли токен использован или нет, и срок его действия уже истек.

Создание тестового пользователя

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

Из-за этого нам понадобится пользователь в базе данных для проверки входа в систему. Давайте создадим его с помощью tinker:

$ php artisan tinker
> User::create(['name' => 'Jane Doe', 'email' => 'test@example.com'])

Маршрут входа в систему

Мы начнем с создания контроллера AuthController, который мы будем использовать для обработки функций входа в систему, верификации и выхода из системы:

$ php artisan make:controller AuthController

Теперь давайте зарегистрируем маршруты входа в систему в файле routes/web.php нашего приложения. Под приветственным маршрутом давайте определим группу маршрутов, которая будет защищать наши маршруты аутентификации с помощью промежуточного ПО, не позволяя людям, уже вошедшим в систему, просматривать их.

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

Route::group(['middleware' => ['guest']], function() {
  Route::get('login', [AuthController::class, 'showLogin'])->name('login.show');
  Route::post('login', [AuthController::class, 'login'])->name('login');
});

Теперь маршруты зарегистрированы, но нам нужно создать действия, которые будут реагировать на эти маршруты. Давайте создадим эти методы в созданном нами контроллере app/Http/Controllers/AuthController.php.

На данный момент наша страница входа в систему будет возвращать представление, расположенное в auth.login (которое мы создадим дальше), и создадим метод входа в систему, к которому мы вернемся после создания формы:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{
  public function showLogin()
  {
    return view('auth.login');
  }

  public function login(Request $request)
  {
    // TODO
  }
}

Мы собираемся использовать систему шаблонов Laravel Blade и Tailwind CSS для наших представлений.

Поскольку основное внимание в этой статье уделяется логике серверной части, мы не будем вдаваться в детали стилизации. Я не хочу тратить время на настройку правильной конфигурации CSS, поэтому мы будем использовать ссылку с CDN, которую мы можем добавить в наш макет, для обработки правильных стилей.

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

Начнем с создания общего макета, который мы можем использовать для всех наших страниц. Этот файл будет находиться в папке resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ $title }}</title>
</head>
<body>
  @yield('content')
  <script src="https://unpkg.com/tailwindcss-jit-cdn"></script>
</body>
</html>

Я отмечу здесь несколько моментов:

  • Заголовок страницы будет установлен переменной $title, которую мы передадим в макет, когда мы расширим его.
  • Директива Blade @yield('content') - когда мы расширяем этот макет, мы будем использовать именованный раздел под названием «content» для размещения нашего содержимого для конкретной страницы.
  • Скрипт TailwindCSS JIT CDN, который мы используем для обработки наших стилей

Теперь, когда у нас есть макет, мы можем создать страницу регистрации в resources/views/auth/login.blade.php:

@extends('layouts.app', ['title' => 'Login'])
@section('content')
  <div class="h-screen bg-gray-50 flex items-center justify-center">
    <div class="w-full max-w-lg bg-white shadow-lg rounded-md p-8 space-y-4">
      <h1 class="text-xl font-semibold">Login</h1>
      <form action="{{ route('login') }}" method="post" class="space-y-4">
        @csrf
        <div class="space-y-1">
          <label for="email" class="block">Email</label>
          <input type="email" name="email" id="email" class="block w-full border-gray-400 rounded-md px-4 py-2" />
          @error('email')
            <p class="text-sm text-red-600">{{ $message }}</p>
          @enderror
        </div>
        <button class="rounded-md px-4 py-2 bg-indigo-600 text-white">Login</button>
      </form>
    </div>
  </div>
@endsection

Разберем поподробнее:

  • Мы начинаем с расширения макета, который мы создали ранее, и передачи ему заголовка «Вход», который будет заголовком нашей вкладки документов.
  • Мы объявляем раздел под названием content (помните @yield ранее?) И помещаем внутрь содержимое нашей страницы, которое будет отображаться в макете.
  • Некоторые базовые контейнеры и стили применяются для центрирования формы в середине экрана.
  • Действие формы указывает на именованный маршрут route('login'), который, если мы помним из файла routes/web.php, является именем, которое мы дали POST-запросу входа в наш контроллер.
  • Мы включаем скрытое поле CSRF с помощью директивы @csrf (подробнее в документации)
  • Мы показываем любые ошибки валидации, предоставленные Laravel, с помощью директивы @error, если они есть.

Если вы загрузите страницу, она должна выглядеть так:

Авторизация без пароля в Laravel

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

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

// app/Http/Controllers/AuthController.php
// near other use statements
use App\Models\User;

// inside class
public function login(Request $request)
{
  $data = $request->validate([
    'email' => ['required', 'email', 'exists:users,email'],
  ]);
  User::whereEmail($data['email'])->first()->sendLoginLink();
  session()->flash('success', true);
  return redirect()->back();
}

Здесь мы делаем несколько вещей:

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

В приведенных выше шагах есть пара незавершенных задач, поэтому нам нужно выполнить их сейчас.

Мы начнем с обновления нашего представления входа в систему, чтобы проверить это логическое значение успеха, скрыть нашу форму и показать пользователю сообщение, если оно присутствует. Вернемся в resources/views/auth/login.blade.php:

@extends('layouts.app', ['title' => 'Login'])
@section('content')
  <div class="h-screen bg-gray-50 flex items-center justify-center">
    <div class="w-full max-w-lg bg-white shadow-lg rounded-md p-8 space-y-4">
      @if(!session()->has('success'))
        <h1 class="text-xl font-semibold">Login</h1>
        <form action="{{ route('login') }}" method="post" class="space-y-4">
          @csrf
          <div class="space-y-1">
            <label for="email" class="block">Email</label>
            <input type="email" name="email" id="email" class="block w-full border-gray-400 rounded-md px-4 py-2" />
            @error('email')
              <p class="text-sm text-red-600">{{ $message }}</p>
            @enderror
          </div>
          <button class="rounded-md px-4 py-2 bg-indigo-600 text-white">Login</button>
        </form>
      @else
        <p>Please click the link sent to your email to finish logging in.</p>
      @endif
    </div>
  </div>
@endsection

Здесь мы просто обернули форму условным выражением.

Мы только что успешно отправили форму?

  • Нет - показать регистрационную форму
  • Да - сообщите пользователю, что его учетная запись была создана, и проверьте электронную почту на наличие ссылки.

Теперь, если вы снова отправите эту форму, вы увидите сообщение об ошибке, в котором говорится, что нам нужно реализовать эту функцию sendLoginLink в модели User. Мне нравится хранить такую логику в самой модели, чтобы мы могли повторно использовать ее в нашем приложении позже.

Откройте app/Models/User.php и создайте пустой метод, чтобы заполнить его место:

public function sendLoginLink()
{
  // TODO
}

Теперь отправьте форму еще раз и убедитесь, что вы видите сообщение об успешном завершении, как показано ниже:

Авторизация без пароля в Laravel

Конечно, вы еще не получили электронное письмо, но теперь мы можем перейти к этому шагу.

Размышляя о подходе к токенам, который мы обсуждали выше, обозначим дальнейший план действий:

  1. Сгенерируйте уникальный токен и прикрепите его к пользователю
  2. Отправьте пользователю электронное письмо со ссылкой на страницу, которая проверяет этот токен.

Мы собираемся сохранить их в таблице с именем login_tokens. Создадим модель и миграцию (-m):

$ php artisan make:model -m LoginToken

Для миграции нам понадобятся:

  • Уникальный токен для URL, который мы генерируем
  • Связь, которая соединит его с запрашивающим пользователем
  • Дата истечения срока действия токена
  • Флаг, который сообщает нам, был ли токен уже использован. Мы собираемся использовать для этого поле отметки времени, так как отсутствие значения в этом столбце скажет нам, использовалось ли оно, и это отметка времени также позволяет нам узнать, когда оно было использовано - двойная пользя!

Откройте сгенерированную миграцию и добавьте необходимые столбцы:

Schema::create('login_tokens', function (Blueprint $table) {
  $table->id();
  $table->unsignedBigInteger('user_id');
  $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
  $table->string('token')->unique();
  $table->timestamp('consumed_at')->nullable();
  $table->timestamp('expires_at');
  $table->timestamps();
});

Не забудьте после этого запустить миграцию:

$ php artisan migrate

Затем обновите нашу новую модель app/Models/LoginToken, чтобы учесть несколько вещей:

Установка для нашего свойства $guarded пустого массива, что означает, что мы не ограничиваем, какие столбцы могут быть заполнены Создать свойство $date, которое будет преобразовывать наши поля expires_at и consmed_at в экземпляры Carbon\Carbon, когда мы будем ссылаться на них в коде php для удобства позже. Наш метод user(), который позволяет нам ссылаться на пользователя, связанного с токеном

class LoginToken extends Model
{
  use HasFactory;

  protected $guarded = [];
  protected $dates = [
    'expires_at', 'consumed_at',
  ];

  public function user()
  {
    return $this->belongsTo(User::class);
  }
}

Также неплохо поместить обратную связь в модель User:

// inside app/Models/User.php
public function loginTokens()
{
  return $this->hasMany(LoginToken::class);
}

Теперь, когда у нас настроена модель, мы можем выполнить первый шаг нашей функции sendLoginLink(), которая создает токен.

Вернувшись в app/Models/User.php, мы собираемся создать токен для пользователя, используя новую связь loginTokens(), которую мы только что создали, и присвоить ей случайную строку с помощью хелпера Str из Laravel и истечения срока действия через 15 минут.

Поскольку мы устанавливаем expires_at и consmed_at как даты в модели LoginToken, мы можем просто передать текущую дату, и она будет преобразована соответствующим образом. Мы также будем хэшировать токен перед тем, как вставить его в базу данных, чтобы в случае взлома этой таблицы никто не мог увидеть необработанные значения токена.

Мы используем воспроизводимый хэш, чтобы мы могли найти его позже, когда это потребуется:

use Illuminate\Support\Str;

public function sendLoginLink()
{
    $plaintext = Str::random(32);
    $token = $this->loginTokens()->create([
      'token' => hash('sha256', $plaintext),
      'expires_at' => now()->addMinutes(15),
    ]);
    // todo send email
}

Теперь, когда у нас есть токен, мы можем отправить пользователю электронное письмо, содержащее ссылку с (открытым текстом) токеном в URL-адресе, который будет подтверждать их сеанс. Маркер должен быть в URL-адресе, чтобы мы могли узнать, для какого пользователя он предназначен.

Перейдем к созданию класса для формирования письма.

$ php artisan make:mail MagicLoginLink

Откройте класс для создания письма в app/Mail/MagicLoginLink.php, и введите следующее:

<?php
namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\URL;

class MagicLoginLink extends Mailable
{
  use Queueable, SerializesModels;

  public $plaintextToken;
  public $expiresAt;

  public function __construct($plaintextToken, $expiresAt)
  {
    $this->plaintextToken = $plaintextToken;
    $this->expiresAt = $expiresAt;
  }

  public function build()
  {
    return $this->subject(
      config('app.name') . ' Login Verification'
    )->markdown('emails.magic-login-link', [
      'url' => URL::temporarySignedRoute('verify-login', $this->expiresAt, [
        'token' => $this->plaintextToken,
      ]),
    ]);
  }
}

Конструктор класса примет маркер открытого текста и дату истечения срока действия и сохранит их в общедоступных свойствах. Это позволит нам использовать его позже в методе build() при его составлении.

Внутри метода build() мы устанавливаем тему письма и говорим ему искать форматированное представление внутри resources/views/emails/magic-login-link.blade.php. Laravel предоставляет некоторые стили по умолчанию для писем, которыми мы вскоре воспользуемся.

Мы также передаем в представление переменную url, которая будет ссылкой, на которую нажмет пользователь.

Это свойство url является временным подписанным URL. Он принимает именованный маршрут, дату истечения срока действия (которую мы хотим использовать в качестве истечения срока действия наших токенов) и любые параметры (в данном случае токен представляет собой нехешированную случайную строку, которую мы сгенерировали). Подписанный URL-адрес гарантирует, что URL-адрес вообще не был изменен, путем хеширования URL-адреса с секретной строкой, известной только Laravel.

Несмотря на то, что мы собираемся добавить проверки в наш маршрут verify-login, чтобы убедиться, что наш токен все еще действителен (на основе свойств expires_at и consmed_at), подписание URL-адреса дает нам дополнительную безопасность на уровне фреймворка, поскольку никто не сможет перебрать принудительно использовать маршрут проверки и входа в систему со случайными токенами, чтобы увидеть, смогут ли они найти тот, который выполняет их вход.

Теперь нам нужно реализовать это представление уценки в resources/views/emails/magic-login-link.blade.php. Вам может быть интересно, почему это расширение .blade.php. Это связано с тем, что даже несмотря на то, что мы пишем в этом файле разметку, мы можем использовать внутри директивы Blade для создания повторно используемых компонентов, которые мы можем использовать в наших электронных письмах.

Laravel предоставляет нам готовые компоненты из коробки, чтобы сразу приступить к работе. Мы используем mail::message, который дает нам макет и призыв к действию через mail::button:

@component('mail::message')
  Hello, to finish logging in please click the link below
  @component('mail::button', ['url' => $url])
    Click to login
  @endcomponent
@endcomponent

Теперь, когда у нас есть содержимое электронной почты, мы можем завершить метод sendLoginLink(), фактически отправив электронное письмо. Мы собираемся использовать фасад Mail, предоставляемый Laravel, чтобы указать электронную почту пользователей, на которую мы ее отправляем, и что содержимое электронной почты должно быть встроено из класса MagicLoginLink, который мы только что закончили настраивать.

Мы также используем queue() вместо send(), чтобы электронное письмо отправлялось в фоновом режиме, а не во время текущего запроса. Убедитесь, что ваша очередь настроен надлежащим образом или что вы используете драйвер синхронизации (это значение по умолчанию), если вы хотите, чтобы это произошло немедленно.

use Illuminate\Support\Facades\Mail;
use App\Mail\MagicLoginLink;

public function sendLoginLink()
{
  $plaintext = Str::random(32);
  $token = $this->loginTokens()->create([
    'token' => hash('sha256', $plaintext),
    'expires_at' => now()->addMinutes(15),
  ]);
  Mail::to($this->email)->queue(new MagicLoginLink($plaintext, $token->expires_at));
}

Если бы вы отправили нашу форму входа, вы бы теперь увидели электронное письмо, которое выглядит следующим образом:

Авторизация без пароля в Laravel

Маршрут проверки

Если вы попытались щелкнуть ссылку, вероятно, вы получили ошибку 404. Это потому, что в нашем электронном письме мы отправили пользователю ссылку на именованный маршрут verify-login, но мы еще не создали его!

Зарегистрируйте маршрут в группе маршрутов внутри routes/web.php:

Route::group(['middleware' => ['guest']], function() {
  Route::get('login', [AuthController::class, 'showLogin'])->name('login.show');
  Route::post('login', [AuthController::class, 'login'])->name('login');
  Route::get('verify-login/{token}', [AuthController::class, 'verifyLogin'])->name('verify-login');
});

Затем мы создадим реализацию внутри нашего класса AuthController с помощью метода verifyLogin:

public function verifyLogin(Request $request, $token)
{
  $token = \App\Models\LoginToken::whereToken(hash('sha256', $token))->firstOrFail();
  abort_unless($request->hasValidSignature() && $token->isValid(), 401);
  $token->consume();
  Auth::login($token->user);
  return redirect('/');
}

Здесь мы делаем следующее:

  • Поиск токена путем хеширования значения открытого текста и сравнения его с хешированной версией в нашей базе данных (выдает 404, если не найден - через firstOrFail())
  • Отмена запроса с кодом состояния 401, если токен недействителен или подписанный URL-адрес недействителен (вы можете пофантазировать здесь, если хотите показать представление или что-то, что позволяет пользователю узнать больше информации, но для этого урока мы просто завершим запрос)
  • Отметка токена как использованного, чтобы его нельзя было использовать снова
  • Авторизация пользователя, связанного с токеном
  • Перенаправление на домашнюю страницу

Мы вызываем пару методов для токена, которых на самом деле еще не существует, поэтому давайте создадим их:

  • isValid() будет истинным, если токен еще не был использован (consmed_at === null) и если срок его действия не истек (expires_at <= now)
  • Мы извлечем просроченные и использованные, проверив их собственные функции, чтобы сделать их более читаемыми.
  • consume() собирается установить для свойства consmed_at текущую метку времени
public function isValid()
{
  return !$this->isExpired() && !$this->isConsumed();
}

public function isExpired()
{
  return $this->expires_at->isBefore(now());
}

public function isConsumed()
{
  return $this->consumed_at !== null;
}

public function consume()
{
  $this->consumed_at = now();
  $this->save();
}

Если бы вы сейчас щелкнули эту ссылку для входа в свой адрес электронной почты, вы должны были быть перенаправлены на /route!

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

Последние штрихи

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

Для начала отредактируйте корневой маршрут по умолчанию в app/web.php, чтобы добавить промежуточное ПО для аутентификации:

Route::get('/', function () {
    return view('welcome');
})->middleware('auth');

Давайте также настроим приветствие по умолчанию, чтобы показать немного информации о нашем вошедшем в систему пользователе, а также предоставить ссылку для выхода. Замените содержимое resources/views/welcome.blade.php следующим:

@extends('layouts.app', ['title' => 'Home'])
@section('content')
  <div class="h-screen bg-gray-50 flex items-center justify-center">
    <div class="w-full max-w-lg bg-white shadow-lg rounded-md p-8 space-y-4">
      <h1>Logged in as {{ Auth::user()->name }}</h1>
      <a href="{{ route('logout') }}" class="text-indigo-600 inline-block underline mt-4">Logout</a>
    </div>
  </div>
@endsection

И, наконец, функционал выхода из системы, который забудет нашу сессию и вернет нас на экран входа в систему. Снова откройте routes/web.php и добавьте этот маршрут в конец файла:

Route::get('logout', [AuthController::class, 'logout'])->name('logout');

И, наконец, нам нужно реализовать действие выхода из системы в нашем AuthController:

public function logout()
{
  Auth::logout();
  return redirect(route('login'));
}

Теперь ваша домашняя страница должна выглядеть так и быть доступна для просмотра только тем, кто вошел в систему:

Авторизация без пароля в Laravel

Заключение

Вот и все! Мы разобрались как реализовать функционал для входа в систему без пароля. Надеюсь вам было интересно.