Laravel 10 – Get User Country And Validate From IP & Zipcode

country validation based on ip and zipcode

Last Updated On - February 20th, 2024 Published On - Jan 11, 2024

In this tutorial, I will take you through creating a user registration system in Laravel 10 to get user country from the IP address or Zipcode and validate(restrict) the user based on the allowed locations that we define in the system.

For example, If I’ve allowed visitors from the US and Canada to register on the website then IP detection will validate the user in the background, and Zipcode validation will be checked based on the zipcode value entered by the visitor.

Step 1: Setup Project And Create Migration

Create a Laravel project using the following command:

composer create-project --prefer-dist laravel/laravel country-validation

When you setup laravel project there is user migration file(2014_10_12_000000_create_users_table.php) inside database/migration folder. Open the file and edit it as below:

public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('fname',50);
            $table->string('lname',50);
            $table->string('email',100)->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->string('zipcode', 10);
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }

Run the migration

php artisan serve


Step 2: Get IP Location

Generate a controller named UserAuthController using the following Artisan command:

php artisan make:controller UserAuthController

Let us implement logic in UserAuthController to load the registration form only if the visitor’s IP is from allowed countries, such as the US and Canada.

Retrieve The IP Address Details of the Visitor

public function getMyIPAddress() {
    // Check if the client's IP address is available in the 'HTTP_CLIENT_IP' header
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'] ?? '';
    } 
    // If not, check if the IP address is available in the 'HTTP_X_FORWARDED_FOR' header
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
    } 
    // If both headers are not present, fallback to using the 'REMOTE_ADDR' header
    else {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    }

    // The variable $ip now holds the user's IP address
    // Additional code can be added here to process or validate the IP address

    // Return the obtained IP address
    return $ip;
}

Explanation

  1. The function checks if the client’s IP address is available in the ‘HTTP_CLIENT_IP’ header. If it is, it assigns the IP address to the variable $ip.
  2. If the ‘HTTP_CLIENT_IP’ header is not available, it checks if the IP address is present in the ‘HTTP_X_FORWARDED_FOR’ header. If found, it assigns the IP address to $ip.
  3. If both headers are not present, the function falls back to using the ‘REMOTE_ADDR’ header to obtain the IP address and assign it to $ip.
  4. The function then returns the obtained IP address.

This code is a common approach to retrieving the user’s IP address, considering different scenarios where the IP address might be stored in various HTTP headers.

Get Location Details From The User’s IP Address

public function getLocationInfo($ip){
    try {
        // Send an HTTP GET request to the IPinfo API with the user's IP address
        $response = Http::get("http://ipinfo.io/$ip/json");

        // Check if the request was successful
        if ($response->successful()) {
            // If successful, return the location details as an associative array
            return $location = [
                'status' => 'success', 
                'data' => $response->json()
            ];
        } else {
            // If the request was not successful, return an error message
            return $location = [
                'status' => 'error',
                'message' => 'Unable to retrieve location data.',
            ];
        }

    } catch (\Throwable $th) {
        // If an exception occurs during the process, rethrow it
        throw $th;
    }
}

Explanation

  1. The function takes the user’s IP address as a parameter.
  2. It uses Laravel’s Http class to send an HTTP GET request to the IPinfo API, appending the user’s IP address to the API endpoint.
  3. It checks if the HTTP request was successful using the successful() method.
  4. If the request was successful, it returns an array with a ‘success’ status and the location details obtained from the API as JSON.
  5. If the request is not successful, it returns an array with an ‘error’ status and a message indicating the inability to retrieve location data.
  6. The function also includes a try-catch block to handle any exceptions that might occur during the process, and it rethrows the exception if one occurs.

This code allows you to get location information based on the user’s IP address using the IPinfo API and handles both successful and unsuccessful scenarios gracefully.



Step 3: Validate IP Location

Load The Registration Form If The Visitor’s IP Is From Allowed Countries

app/Http/controller/UserAuthController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class UserAuthController extends Controller
{
    public function showRegistrationForm()
    {
        // Get the visitor's IP address
        $visitorIP = $this->getMyIPAddress();

        // Get location details based on the IP address
        $locationInfo = $this->getLocationInfo($visitorIP);

        // Check if the visitor's country is allowed (e.g., US or Canada)
        $allowedCountries = ['US', 'CA'];

        if (in_array($locationInfo['data']['country'], $allowedCountries)) {
            // If the country is allowed, load the registration form
            return view('auth.signup');
        } else {
            // If the country is not allowed, redirect or show an error message
            return redirect()->back()->with('error', 'Registration not allowed from your country.');
        }
    }

    // getMyIPAddress and getLocationInfo functions as described above
    // ...

}

resources/views/auth/signup.blade.php

<form method="POST" action="{{ route('auth.register') }}" id="commonForm">
        @csrf
        <div class="form-group position-relative clearfix">
            <input name="fname" type="text" value="{{ old('fname') }}" class="form-control @if($errors->has('fname')){{'err-border'}}@endif" placeholder="First Name" aria-label="Firstname" />
            
        </div>
        <div class="form-group position-relative clearfix">
            <input name="lname" type="text" value="{{ old('lname') }}" class="form-control @if($errors->has('lname')){{'err-border'}}@endif" placeholder="Last Name" aria-label="Lastname" />
            
        </div>
        <div class="form-group position-relative clearfix">
            <input name="email" type="email" value="{{ old('email') }}" class="form-control @if($errors->has('email')){{'err-border'}}@endif" placeholder="Email Address" aria-label="Email Address"  />
        </div>
        <div class="form-group position-relative clearfix">
            <input name="zipcode" type="text" value="{{ old('zipcode') }}" class="form-control @if($errors->has('zipcode')){{'err-border'}}@endif" placeholder="Zip Code (US & Canadian Zip Codes Only)" aria-label="Zip Code"  />
        </div>
            
        <div class="form-group clearfix mb-0">
            <button type="submit" class="btn btn-primary btn-lg btn-theme" />Sign Up</button>
        </div>
        
    </form>


Step 4: Validate Zipcode Location

Install Laravel Postal Code Validation Package

Firstly, let’s install the axlon/laravel-postal-code-validation package to enable postal code validation for specific countries. Open your terminal and run the following command:

composer require axlon/laravel-postal-code-validation

If package discovery is enabled, no additional steps are needed. Otherwise, register the package manually in the config/app.php file under the ‘providers’ array:

'providers' => [
    // ...
    Axlon\PostalCodeValidation\ValidationServiceProvider::class,
    // ...
],

Implement The Registration Logic

namespace App\Http\Controllers;

use App\Models\Roles;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Session;
use App\Jobs\SendEmailJob;
use App\Models\User;

class UserAuthController extends Controller
{
    public function register(Request $request)
    {
        // Get user's IP address and location information
        $ip = $this->getMyIPAddress();
        $userLocationInfo = $this->getLocationInfo($ip);

        // Check if the user's country is not US or CA, and redirect if true
        if (isset($userLocationInfo['data']['country']) && !in_array($userLocationInfo['data']['country'], ['US', 'CA'])) {
            return redirect('register');
        }

        // Validation rules and custom error messages
        $validator = Validator::make(
            $request->all(),
            [
                'fname' => 'required|max:50',
                'lname' => 'required|max:50',
                'email' => 'required|email|unique:users',
                'zipcode' => 'required|postal_code:CA,US'
            ],
            [
                'fname.required' => 'Firstname is required.',
                'fname.max' => 'Firstname must not be greater than 50 characters',
                'lname.required' => 'Lastname is required.',
                'lname.max' => 'Lastname must not be greater than 50 characters',
                'zipcode.required' => 'Zip Code is required.',
                'zipcode.postal_code' => 'Only US and Canadian Zip Codes are allowed'
            ]
        );

        // If validation fails, return back with input and validation errors
        if ($validator->fails()) {
            return back()->withInput($request->input())->withErrors($validator->errors());
        }

        // Generate a random password and encrypt it
        $password = substr(Hash::make(Str::random(8)), 0, 10);

        // Prepare user data for creation
        $data = $request->all();
        $data['password'] = bcrypt($password);

        // Create the user
        $user = User::create($data);

        // Prepare data for email notification
        $emailData = [
            'subject' => 'Registration Successful',
            'name' => $user->fname . ($user->lname ? ' ' . $user->lname : ''),
            'email' => $user->email,
            'password' => $password,
        ];

        // Dispatch a job to send an email with registration details
        SendEmailJob::dispatch($emailData);

        // Redirect to the success page and show suceess message
        return redirect('registration-success')->with('success','Registered successfully');
    }
}

Explanation

  1. The method begins by retrieving the user’s IP address and location information. If the country is not the US or Canada, it redirects the user to the registration page.
  2. It defines validation rules using Laravel’s Validator based on the provided request data, with specific rules for each field (e.g., ‘fname’, ‘lname’, ’email’, ‘zipcode’).
  3. If the validation fails, it returns to the registration page with the input data and validation errors.
  4. If validation passes, it generates a random password, encrypts it, and prepares the user data for insertion into the database.
  5. The user is created in the database, and an email notification job is dispatched to send registration details to the user.
  6. Finally, it redirects the user to a registration success page with a “Registered successfully” flash message(one-time value stored in the session variable).


FAQs

Why do we need to validate the user’s country using IP and Zipcode?

Validating a user’s country using IP and Zipcode can help ensure that the user is indeed from the location they claim to be. This can be particularly useful for businesses that offer location-specific services or need to comply with certain regional laws and regulations.

What is the Laravel Postal Code Validation Package and why is it necessary?

The Laravel Postal Code Validation Package is a tool that allows you to validate postal codes based on the country (allowing only specific countries – Canadian and US in the above example). It’s necessary because postal code formats can vary greatly from country to country. By using this package, you can ensure that the user enters a valid postal code for their country.

What happens if a user tries to register from a country that is not allowed?

If a user tries to register from a country that is not allowed, they will be unable to access the registration form. This can help prevent unauthorized access to your application.

Why use a job for email sending instead of sending it directly in the registration method?

Sending an email can be a time-consuming process and can slow down the response time of your application. By using a job to send the email, you can offload this task to a queue and improve the response time of your application. This also provides a better user experience as the user won’t have to wait for the email to be sent before they can continue using your application.

Why use a Mailable for the email content?

To separate the email structure from the application logic, promoting maintainability and readability.

How can I add more countries to the list of allowed countries?

You can add more countries to the list of allowed countries by updating the array of allowed countries in your code. Make sure to use the correct two-letter country code for each country you want to add.



Signing-off note

You’ve successfully implemented a user registration system in Laravel with IP-based country restrictions and email notification. This tutorial covered the installation of a postal code validation package, creating a controller for registration logic, and defining a job to handle email sending asynchronously for the registration email.