DEV Community

Ahmed Mansoor
Ahmed Mansoor

Posted on

1

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.

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay