WordPress Security

Add extra security to stop brute force attacks (logins).

These measures can reduce login attempts dramatically from 400 a day to none. 

Password Protect wp-login.php

Add this to htaccess

# Stop Apache from serving .ht* files
<Files ~ "^\.ht">
  Order allow,deny
  Deny from all
</Files>
# Protect wp-login.php
<Files wp-login.php>
	AuthUserFile /filelocation/.htpasswd
	AuthName "Private access"
	AuthType Basic
	Require valid-user
</Files>

Deny Access to No Referrer Requests

When your readers comment, the wp-comments-post.php file is accessed, does its thing, and creates the post. The user’s browser will send a “referral” line about this.

When a spam-bot comes in, it hits the file directly and usually does not leave a referrer. This allows for some nifty detection and action direct from the server. If you are not familiar with Apache directives, then write the following in your root directory .htaccess file:

# Stop spam attack logins and comments
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteCond %{REQUEST_METHOD} POST
	RewriteCond %{REQUEST_URI} .(wp-comments-post|wp-login)\.php*
	RewriteCond %{HTTP_REFERER} !.*domain.com.* [OR]
	RewriteCond %{HTTP_USER_AGENT} ^$
	RewriteRule (.*) http://%{REMOTE_ADDR}/$1 [R=301,L]
</ifModule>

This will:

  1. Detect when a POST is being made
  2. Check to see if the post is on wp-comments-post.php
  3. Check if the referrer is in your domain or if no referrer
  4. Send the spam-bot BACK to its originating server’s IP address.

Disabling Xmlrpc.php

The biggest issues with XML-RPC are the security concerns that arise. The issues aren’t with XML-RPC directly, but instead how the file can be used to enable a brute force attack on your site.

The first is using brute force attacks to gain entry to your site. An attacker will try to access your site using xmlrpc.php by using various username and password combinations. They can effectively use a single command to test hundreds of different passwords. This allows them to bypass security tools that typically detect and block brute force attacks.

The second was taking sites offline through a DDoS attack. Hackers would use the pingback feature in WordPress to send pingbacks to thousands of sites instantaneously. This feature in xmlrpc.php gives hackers a nearly endless supply of IP addresses to distribute a DDoS attack over.

# Block WordPress xmlrpc.php requests
<Files xmlrpc.php>
order deny,allow
deny from all
</Files>

Laravel Data Seeding

Creating a simple seeder

You’ll find your seeder files under database > seeders

Create a new one with this:

php artisan make:seeder UsersTableSeeder

Open new file and add to run function:

        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => Str::random(10).'@codingfaculty.com',
            'password' => bcrypt('secret')
        ]);

At the top of document add:

use DB;
use Str;

Now run:

php artisan db:seed

Now look at your database to see new user created.

Creating a more advanced seeder with factories

Go to database > factories to see the default one for users. We’ll use this one inside our DatabaseSeeder.php to create many users at once. Under run function:

<?php
namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        User::factory()->count(10)->create(); 
    }
}

Then run:

php artisan db:seed

Check your database and you’ll see 10 new users with real names.

Create Factories for all Database Tables

Edit user factory:

    public function definition()
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
            'country_id' => fake()->numberBetween(1,3),
        ];
    }
php artisan make:factory PostFactory

Inside new factory add to definition function:

    public function definition()
    {
        return [
            'user_id' => 1,
            'title' => fake()->sentence(7,11),
            'content' => fake()->paragraphs(rand(10,1),true),
            'path' => fake()->randomElement(['first.jpg','second.jpg','third.jpg']),
        ];
    }

Create one for Role:

php artisan make:factory RoleFactory
    public function definition()
    {
        return [
            'name' => fake()->randomElement(['administrator','author','subscriber']),
        ];
    }

Create one for photo:

php artisan make:factory PhotoFactory
    public function definition()
    {
        return [
            'path' => 'placeholder.jpg',
            'imageable_id' => fake()->numberBetween(1,3),
            'imageable_type' => fake()->randomElement(['App\Models\User','App\Models\Post']),
        ];
    }

Create one for country:

php artisan make:factory CountryFactory
    public function definition()
    {
        return [
            'name' => fake()->randomElement(['Canada','United States','United Kingdom']),
        ];
    }

Add to DatabaseSeeder.php:

<?php
namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Post;
use App\Models\Role;
use App\Models\Photo;
use App\Models\Country;
use DB;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        DB::statement('SET FOREIGN_KEY_CHECKS=0');
        DB::table('users')->truncate();
        DB::table('posts')->truncate();
        DB::table('roles')->truncate();
        DB::table('photos')->truncate();
        DB::table('countries')->truncate();

        User::factory()
            ->count(3)
            ->has(Post::factory()->count(2)->has(Photo::factory()->count(1)))
            ->has(Photo::factory()->count(1))
            ->create();
        Role::factory()->count(3)->create(); 
        Country::factory()->count(3)->create();
    }
}

Then run:

php artisan db:seed

Laravel Frontend

Adding Bootstrap plus Login & Registration

First require Laravel UI

composer require laravel/ui

Then you need to choose what front end to include: Bootstrap, Vue or React

php artisan ui bootstrap

For react you need first:

php artisan ui scaffolding

Do bootstrap for this version though.

Now it’ll ask you to run (You’ll need to run from Git Bash):

npm install && npm run dev
php artisan serve
php artisan serve

Create the webpack config file:

touch webpack.mix.js
npm install sass-loader@^12.1.0 resolve-url-loader@^5.0.0 --save-dev --legacy-peer-deps

Open the file and add:

let mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');

Now compile your css and js to the public folder:

npm install laravel-mix@latest
npx mix

We can’t see bootstrap yet. We’ll need to add to welcome.blade.php

Add link:css and tab to the top of document and then href="{{asset('css/app.css')}}" which will link to the public folder automatically.

Now add this button anywhere to body:

<button class="btn btn-danger">Hi</button>

You should now see a red styled button which means bootstrap css is working.

Add login and registration page

php artisan ui bootstrap --auth

Then after run:

npm install && npm run dev

Now if you go to /login you’ll see a login page.

How to add templates to Laravel projects

Move js, css and vendor folder into the public folder in Laravel project.

Then copy the contents blank.html and put into layouts/admin.blade.php. Then update the <h1> tag below <!– Page Heading –> with @yield('content')

Create another template under admin/index.blade.php and paste this into it:

@extends('layouts.admin')

@section('content')

<h1>Admin</h1>

@endsection

Create a route:

Route::get('/admin', function () {
    return view('admin.index');
});

Check /admin to see if you see your Admin template.

Now update your admin.blade.php with dynamic asset links: {{asset('vendor/fontawesome-free/css/all.min.css')}}

Create admin/partials/_navbar.blade.php. Then cut out sidebar from admin.blade.php and paste into this file.

Go back to admin.blade.php and in its place paste:

@include('admin.partials._navbar')

Refresh to view

This is how to take a template and break it out into parts in Laravel.

Sending Email / Api

Sign up for Mailgun

Then find your API keys

Updating env file

Open config/mail.php to configure beginning of email:

MAIL_HOST should be smtp.mailgun.org

Update from address to something like:

    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'myemail@domain.com'),
        'name' => env('MAIL_FROM_NAME', 'Christine'),
    ],

Open up config/services.php to find mailgun variables needed to set up env file. MAILGUN_DOMAIN, MAILGUN_SECRET

Open env file and delete all mail info and add these lines:

MAIL_DRIVER=mailgun
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@blah.mailgun.org
MAIL_PASSWORD=passwordhere
MAIL_ENCRYPTION=tls
MAILGUN_DOMAIN=sandboxa63bbae0e8f448b7a7943f800a2a166a.mailgun.org
MAILGUN_SECRET=281329ff11f62a513a665d35f7763aea-4dd50799-322083f3
Get the password and username from clicking SMTP option inside Mailgun. 

Create a new view /emails/test.blade.php. Then add to route:

Route::get('/', function () {
    // return view('welcome');
    $data = [
        'title' => 'This page will send an email',
        'content' => 'It\'s going to happen, just wait!',
    ];
    Mail::send('emails.test', $data, function($message){
        $message->to('emailhere', 'Christine')->subject('This is the subject');
    });
});

Add this to your test.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>Document</title>
</head>
<body>
    <h1>{{$title}}</h1>
    <h1>{{$content}}</h1>
</body>
</html>

Then add a requirement:

composer require guzzlehttp/guzzle
Any changes made to email do this: php artisan config:clear

You need to go into vender > guzzlehttp > guzzle > src > Client.php and find verify.. change it to false.. this will allow without ssl enabled in dev.

Now you’ll be able to visit the homepage and you’ll get sent an email!

If you go back to Mailgun you can go to Sending > Logs to see the emails went out.

Laravel Sessions

Setting and reading sessions

Create a HomeController:

php artisan make:controller --resource HomeController

Your route /home will point to the HomeController:

Route::get('/home',[HomeController::class, 'index']);

Now add a session to index:

    public function __construct(){
        $this->middleware('auth');
    }
    public function index(Request $request)
    {
        $request->session()->put(['christine'=>'hello christine']);
        session(['peter'=>'hello peter']);
        echo $request->session()->get('christine');
        //return view('welcome');
    }

Above are two ways of saving sessions. We are only returning the first one when visiting the /home URL.

Global session function deleting

Add a method to forget the session:

    public function index(Request $request)
    {
        $request->session()->put(['christine'=>'hello christine']);
        session(['peter'=>'hello peter']);
        $request->session()->forget('christine');
        return $request->session()->all();
        //return view('welcome');
    }

To delete all sessions use this:

        $request->session()->flush();

Flashing data

This type of session will show user the session once (flash it to them) and then forget it.

        $request->session()->flash('message','Post has been created');
        return $request->session()->get('message');

Two more types are reflash and keep:

        $request->session()->reflash();
        $request->session()->keep('message');

Middleware Security/Protection

Open up app/Http/Kernel.php and find protected $routeMiddleware

Underneath this you’ll see the different shorthands of calling the classes like:

'auth' => \App\Http\Middleware\Authenticate::class,

Which you can find under app/Http/Middleware/Authenticate.php

Now we can create our own middleware with this command:

php artisan make:middleware RoleMiddleware

Open up app/Http/Middleware/RoleMiddleware.php

Registering a new middleware and using it

To put in maintenance mode type:

php artisan down

Then put back up

php artisan up

Now register the RoleMiddleware in Kernel.php.. add to end of this:

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'role' => \App\Http\Middleware\RoleMiddleware::class,
    ];

Create a route for the middleware that’s basic like:

Route::get('/admin/user/roles', ['middleware'=>'role', function () {
    return "Middleware role";
}]);

Now update RoleMiddleware to handle this request to the middleware:

    public function handle(Request $request, Closure $next)
    {
        return redirect('/');
        //return $next($request);
    }

Now going to the /admin/user/roles URL will simply redirect the user the homepage.

Middleware – roles, migration and relations setup

Create a Role model to store the role of user:

php artisan make:model Role -m

Open up user migration and add this:

$table->integer('role_id');

In new role migration add:

$table->string('name');

Then

php artisan migrate:refresh

Open Role model:

    protected $fillable = [
        'name',
    ];

Open User model:

    public function role(){
        return $this->belongsTo('App\Models\Role');
    }

Custom Method

Method 1

Create another middleware:

php artisan make:middleware IsAdmin

Now open up Kernel to add the middleware:

'isAdmin' => \App\Http\Middleware\IsAdmin::class,

Now we’ll need to create roles in our database to use with this case. Add admin and subscriber. Then register a user on the web part at /register.

Create a relationship in User model called isAdmin

    public function isAdmin(){
        if($this->role->name == 'admin' ){
            return true;
        }
        return false;
    }

Now add a route to check whether your current logged in user is administrator (check database to see if it is first!)

Route::get('/', function () {
    $user = Auth::user();
    if($user->isAdmin()){
        echo 'this user is an administrator';
    }
    //return view('welcome');
});

Now change the user role to see if it works when not an administrator as well.

Method 2

Return homepage route back to original:

Route::get('/', function () {
    return view('welcome');
});

Add a redirect instead to the middleware when user role is administrator for IsAdmin:

use Illuminate\Support\Facades\Auth;
    public function handle(Request $request, Closure $next)
    {
        $user = Auth::user();
        if(!$user->isAdmin()){
            return redirect('/');
        }
        return $next($request);
    }

Next create a route that calls a new Controller:

Route::get('/admin', 'AdminController@index');

Create controller

php artisan make:controller AdminController

Add to AdminController:

    public function __construct(){
        $this->middleware('isAdmin');
    }
    public function index(){
        return "you are an administrator because you are seeing this page";
    }

Now if you are an admin and visit /admin you’ll see the above message. If you aren’t, you’ll be redirected to the homepage.

Forms Login

Creating the login system

In Laravel 8 things have changed for the authentication system. 

First you need use your composer :

composer require laravel/jetstream

Then you have to create a new project:

laravel new login --jet

Which jetstream package do you prefer 0 livewire 1 inertia. Choose livewire here. Inertia when you want to use React.

Prepare database in phpmyadmin and make migrate:

php artisan migrate

Now, you can go to the registration page:

http://127.0.0.1:8000/register

Register a user and also try logging out and in again.

Login creation overview

Check the default route list it created:

php artisan route:list

Open up your controller folder to view all the files created. Then check your database to see the user created and the remember me token created for them.

Retrieving authenticated user data

Update routes to see new message on homepage:

Route::get('/', function () {
    if(Auth::check()){
        return 'the user is logged in';
    }
    //return view('welcome');
});

And this is attempting to go back to page where you tried to access but redirects to login page:

Route::get('/', function () {
    // if(Auth::check()){
    //     return 'the user is logged in';
    // }
    //return view('welcome');

    if(Auth::attempt(['username'=>$username,'password'=>$password])){
        return redirect()->intended();
    }
});

Forms Uploading Files

Modifying our create view for file input

{!! Form::open(['method'=>'POST','route'=>'posts.store','files'=>true]) !!}
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::file('file',['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Create Post',['class'=>'btn btn-primary']) !!}
    </div>
{!! Form::close() !!}

You are adding a files = true to the form tag and then adding a file field.

Retrieving File Data

Update store function to just return the file name and comment out validation from CreatePostRequest

    public function store(CreatePostRequest $request)
    {
        $file = $request->file('file');
        echo $file;
        echo '<br>';
        echo $file->getClientOriginalName();

        echo '<br>';
        echo $file->getClientSize();
    }

The first shows the temporary file name and the second shows the file name from your computer.

Persisting File Data into the Database

Create a migration to store the file info to database:

php artisan make:migration add_path_column_to_posts --table=posts

Open up and add these to up and down functions:

$table->string('path');
$table->dropColumn('path');

Then migrate:

php artisan migrate

Update your Post model to allow path column for mass migration:

    protected $fillable = [
        'title',
        'content',
        'user_id',
        'path'
    ];

Now edit your store function in PostsController:

    public function store(CreatePostRequest $request)
    {
        $input = $request->all();
        if($file = $request->file('file')){
            $name = $file->getClientOriginalName();
            $file->move('images',$name);
            $input['path'] = $name;
        }
        Post::create($input);
    }

Also if you check your public folder, you’ll see an images folder has been created with the images name.

Displaying images and using accessors to make it easy

Update your views post index page:

<ul>
    @foreach($posts as $post)
        <div class="image-container">
            <img height="100" src="/images/{{$post->path}}" alt="" />
        </div>
        <li><a href="{{route('posts.show',$post->id)}}">{{$post->title}}</a></li>
    @endforeach
</ul>

Let’s create a simple accessor that puts the directory of the images in the Post model. In Post model:

    public $directory = "/images/";public $directory = "/images/";
    public function getPathAttribute($value){
        return $this->directory.$value;
    }

Now change index.blade.php to:

<ul>
    @foreach($posts as $post)
        <div class="image-container">
            <img height="100" src="{{$post->path}}" alt="" />
        </div>
        <li><a href="{{route('posts.show',$post->id)}}">{{$post->title}}</a></li>
    @endforeach
</ul>

Database Some More Model Manipulation

Laravel includes Carbon. But to add to another project using composer, do composer require nesbot/carbon.

Dates

use Carbon\Carbon;
Route::get('/dates', function(){
    $date = new DateTime('+1 week');
    echo $date->format('m-d-Y');
    echo '<br>';
    echo Carbon::now()->addDays(10)->diffForHumans();
    echo '<br>';
    echo Carbon::now()->subMonths(5)->diffForHumans();
    echo '<br>';
    echo Carbon::now()->yesterday();
});

Accessors

Route::get('/getname', function(){
    $user = User::find(1);
    echo $user->name;
});

Update your User model to manipulate the output anytime the user’s name is used. Format here is important, needs to start with get, then the attribute name and then the word attribute.

    public function getNameAttribute($value) {
        return strtoupper($value);
    }

Mutators

Mutators will manipulate the data before its sent to the database.

    public function setNameAttribute($value) {
        $this->attributes['name'] = strtolower($value);
    }

Add to route:

Route::get('/setname', function(){
    $user = User::find(1);
    $user->name = 'William';
    $user->save();
});

Now if you check the database after visiting setname, you should see william is in lowercase.

Query Scope

The usual way to query is to do something like this (open PostController):

    public function index()
    {
        $posts = Post::orderBy('id','desc')->get();
        return view('posts.index', compact('posts'));
    }

Instead we are going to make orderBy shorter by adding a function to our Post model for queries.

    public static function scopeMeek($query) {
        return $query->orderBy('id','desc')->get();
    }

Update PostController:

    public function index()
    {
        $posts = Post::meek();
        return view('posts.index', compact('posts'));
    }

Laravel Forms Package and Validation

Installing the package

You need to get the “laravel illuminate/html collective” which you can google.

First we’ll add it manually. Go here:

https://laravelcollective.com/docs/6.x/html

Edit your project’s composer.json file to require laravelcollective/html. Find your require block and add:

"laravelcollective/html": "6.*"

In terminal:

composer update

Now you need to add to any Model that’s going to use it. In this case add to the Post model:

use Collective\Html\Eloquent\FormAccessible;

Modifying the create form

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open() !!}
    <input type="text" name="title" placeholder="Enter title" />
    <input type="submit" name="submit" />
{!! Form::close() !!}

@endsection
Remove the token as it'll create it for you. If you inspect your page form, you should see the token is added automatically. However if you submit the form you'll get an error. Its sending to the wrong URL.

Okay make these edits:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open(['method'=>'POST','route'=>'posts.store']) !!}
    <input type="text" name="title" placeholder="Enter title" />
    <input type="submit" name="submit" />
{!! Form::close() !!}

@endsection

Next add fields using the package like this:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open(['method'=>'POST','route'=>'posts.store']) !!}
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Create Post',['class'=>'btn btn-primary']) !!}
    </div>
{!! Form::close() !!}
@endsection

Modifying the edit/delete form

@extends('layouts.app')

@section('content')
<h1>Edit Post</h1>
{!! Form::model($post,['method'=>'PATCH','route'=>['posts.update',$post->id]]) !!}
    {!! Form::label('title','Title:') !!}
    {!! Form::text('title',null,['class'=>'form-control']) !!}
    
    {!! Form::submit('Update Post',['class'=>'btn btn-info']) !!}
{!! Form::close() !!}

<!-- <form method="post" action="/posts/{{$post->id}}"> -->
{!! Form::open(['method'=>'DELETE','route'=>['posts.destroy',$post->id]]) !!}
    {!! Form::submit('Delete Post',['class'=>'btn btn-danger']) !!}
{!! Form::close() !!}

@endsection

Basic Validation

Update the store function in PostController like this:

    public function store(Request $request)
    {
        $this->validate($request,[
            'title'=>'required|max:4'
        ]);
        $post = new Post;
        $post->title = $request->title;
        $post->save();
        return redirect('/posts');
    }

Now the form won’t enter without a value.

Displaying Errors

The validate function will create an errors variable when it fails which can be used in the views. So in our create.blade.php add this part:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open(['method'=>'POST','route'=>'posts.store']) !!}
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Create Post',['class'=>'btn btn-primary']) !!}
    </div>
{!! Form::close() !!}

@if(count($errors)>0)
    <div class="alert alert-danger">
        @foreach($errors->all() as $error)
        <li>{{$error}}
        @endforeach
    </div>
@endif
@endsection

You should see errors added to bottom of form now.

Advance validation

Instead of creating the validation inside the store function, we are going to create a validation class that we’ll call inside the store function.

To see all your functions available type:

php artisan

You’ll see make:request Create a new form request class which is what we want to do. So next type this command:

php artisan make:request CreatePostRequest

You’ll find this file under App\Http\Requests. Open it and change authorize function to return true since we aren’t creating something that needs it.

Update rules function like so:

    public function rules()
    {
        return [
            'title'=>'required|max:4',
        ];
    }

That’s it! Very simple. Now we need to use it in our PostsController.

use App\Http\Requests\CreatePostRequest;
    public function store(CreatePostRequest $request)
    {
        $post = new Post;
        $post->title = $request->title;
        $post->save();
        return redirect('/posts');
    }