DEV Community

Ahmed Mansoor
Ahmed Mansoor

Posted on

Sending Mail in Laravel and securing it with Google reCAPTCHA

contact form

I spent some time digging up the internet on how to send mail in Laravel and integrate reCaptcha to secure. Although the documentation is pretty straightforward, I wanted some samples and a step-by-step guide. So, here I’ll show you how to integrate reCAPTCHA into your Laravel application to enhance the security of your mail forms.

Note:
I’m using Laravel 9 and will be programmatically invoking the challenge when using reCAPTCHA v3. You may automatically bind the challenge to a button.

  1. Generating necessary files: the Model, Migration, Controller, and Markdown Mailable
  2. Mail setup
  3. reCAPTCHA setup
  4. Form view file

1. Generating necessary files: the Model, Migration, Controller, and Markdown Mailable

php artisan make:model ContactMail -mrc
Enter fullscreen mode Exit fullscreen mode
php artisan make:mail ContactMail --markdown=emails.contact-mail
Enter fullscreen mode Exit fullscreen mode

2. Mail setup

Add the mail host, port, address .etc to the .env file.

MAIL_MAILER=log
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=
MAIL_FROM_ADDRESS="email@email.com"
MAIL_FROM_NAME="${APP_NAME}"
Enter fullscreen mode Exit fullscreen mode

ContactMail.php
Update the constructor.

   public $data;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($data)
    {
        $this->data = $data;
    }
Enter fullscreen mode Exit fullscreen mode

contact-mail.blade

<x-mail::message>
email: {{ $data->email }}
**{{ $data->subject }}**<br>
{{ $data->message }} <br>
</x-mail::message>
Enter fullscreen mode Exit fullscreen mode

web.php (route)

Route::name('contact.')
    ->prefix('contact/')
    ->group(function () {
        Route::get('', 'ContactMailController@index')->name('index');
        Route::post('store', 'ContactMailController@store')->name('store');
    });
Enter fullscreen mode Exit fullscreen mode

3. reCAPTCHA setup

Register your reCAPTCHA v3 keys on the reCAPTCHA Admin console here. Add it to your .env file.

RECAPTCHA_SITE_KEY=<paste key here>
RECAPTCHA_SECRET_KEY=<paste key here>
Enter fullscreen mode Exit fullscreen mode

ContactMailController.php

/**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('pages.contact.index');
    }
Enter fullscreen mode Exit fullscreen mode
 /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // form validation
        $data = $request->all();
        $rules = [
            'email' => 'nullable|email',
            'subject' => 'required',
            'message' => 'required',
        ];

        $validator = Validator::make($data, $rules);

        // if form validation fails
        if ($validator->fails()) {
            return Response::json(array(
                'validation' => false,
                'message' => $validator->getMessageBag()->toArray()

            ), 200); // 400 invalid requests
        }

        // verify and get validation
        $response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
            'secret' => env('RECAPTCHA_SECRET_KEY'),
            'response' => $request->recaptchaToken,
        ]);

        $recaptchaResponse = $response->json();

        // if captcha valid
        if ($recaptchaResponse['success'] == true) {
            ContactMail::create($request->all());

            $message = [
                'success' => true,
                'message' => 'Thank you for taking the time to report your concerns.',
            ];
            return response()->json($message, 200);
        }
        // if captcha invalid
        elseif ($recaptchaResponse['success'] == false) {
            $message = [
                'success' => false,
                'message' => 'You a robot?',
            ];
            return response()->json($message, 200);
        } else {
            $recaptchaFail = 'Something went wrong.';
            return response()->json($recaptchaFail, 200);
        }
    }
Enter fullscreen mode Exit fullscreen mode

You got to import the necessary facades.

4. Form view file

contact.blade

<form id="contactForm" method="POST" action="{{ route('contact.store') }}" class="flex flex-col space-y-4">
      {{ csrf_field() }}
      <div class="row">
          <div class="col-md-6 flex flex-col space-y-5">
              <div class="flex flex-row w-full space-x-3 justify-between">
                  <!-- from Email -->
                  <div class="w-full col-md-6">
                      <div class="form-group flex flex-col space-y-2">
                          <label>From <small class="p-0.5 px-1 rounded-md bg-gray-100 text-gray-500">optional</small></label>
                          <input id="email" type="email" name="email" placeholder="Your email address" value{{old('email')}}"
                          class="hover:shadow bg-gray-50 border
                      border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary
                      focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
                      dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary
                      dark:focus:border-primary dark:shadow-sm-light">
                      </div>
                  </div>
              </div>
          </div>
      </div>
      <div class="col-md-6 flex flex-col space-y-5">
          <!-- subject -->
          <div class="form-group space-y-2">
              <label for="subject">Subject <small class="p-0.5 px-1 rounded-md bg-sky-100 text-sky-500">required</small></label>
              <input type="text" id="subject" name="subject" value="{{ old('subject') }}"
              class="hover:shadow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary
              focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
              dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary
              dark:focus:border-primary dark:shadow-sm-light">
          </div>
      </div>
      <div class="row">
          <div class="col-md-12">
              <div class="form-group flex flex-col space-y-2">
                  <label>Message <small class="p-0.5 px-1 rounded-md bg-sky-100 text-sky-500">required</small></label>
                  <textarea id="message" name="message" rows="5" required class="hover:shadow bg-gray-50 border
                  border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary
                  focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
                  dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary
                  dark:focus:border-primary dark:shadow-sm-light">{{ old('message') }}</textarea>
              </div>
          </div>
      </div>
      <div id="success-message"></div>
      <div class="form-group">
          <button id="submit-button"
              data-sitekey="{{env('RECAPTCHA_SITE_KEY')}}"
              data-callback='onSubmit'
              data-action='submit'
              class="g-recaptcha btn-primary">
              <span id="submit-text">Report</span>
          </button>
      </div>
  </form>
Enter fullscreen mode Exit fullscreen mode
<script>
    function onSubmit(token) {
        var bodyFormData = {
            'email' : $('#email').val(),
            'subject' : $('#subject').val(),
            'message' : $('#message').val(),
            'recaptchaToken': token,
        };
        axios({
                method: "post",
                url: "{{route('contact.store')}}",
                data: bodyFormData,
            })
        .then(function (response) {
            // if form validation fails
            if (response.data.validation === false) {
                let messages = response.data.message;
                for (let key in messages) {
                    if (messages.hasOwnProperty(key)) {
                    let errorMessage = messages[key][0];
                    let formField = document.getElementById(key);
                    formField.classList.add('border-gray-300');

                    let errorElement = document.createElement('div');
                    errorElement.classList.add('text-sm','text-orange-500');
                    errorElement.innerHTML = errorMessage;

                    formField.parentNode.appendChild(errorElement);
                    }
                }
                }
            // if ok
            else if(response.data.success == true) {
                var message = response.data.message;
                var successMessage = "<div class='bg-primary bg-opacity-10 text-primary p-4 text-center rounded-lg'>" + message + "</div>";
                $("#success-message").html(successMessage);
                setTimeout(function() {
                    $('#success-message').delay(5000).fadeOut(1000);
                }, 5000);
            }
            // if form validation fails
            else if(response.data.success == false) {
                var message = response.data.message;
                var successMessage = "<div class='bg-orange-500 bg-opacity-10 text-orange-500 p-4 text-center rounded-lg'>" + message + "</div>";
                $("#success-message").html(successMessage);
                setTimeout(function() {
                    $('#success-message').delay(5000).fadeOut(1000);
                }, 5000);
            }
        })
         // if any other error
        .catch(function (error) {
            var message = 'Something went wrong.';
            var successMessage = "<div class='bg-orange-500 bg-opacity-10 text-orange-500 p-4 text-center rounded-lg'>" + message + "</div>";
            $("#success-message").html(successMessage);
            setTimeout(function() {
                $('#success-message').delay(5000).fadeOut(1000);
            }, 5000);
        });
    }

</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Integrating Google reCAPTCHA into your Laravel mail forms is an effective solution for enhancing the security of your web application. This guide provides a step-by-step approach for adding reCAPTCHA, making it easy for developers of all skill levels to send secure emails.

Top comments (0)