Cyberithub

Laravel Authentication [Complete Tutorial with examples]

Advertisements

Authentication is a critical component of web application security, ensuring that only authorized users can access certain resources or perform specific actions. Laravel, a popular PHP framework, provides robust and flexible mechanisms for implementing authentication. Authentication in Laravel refers to the process of identifying and verifying users before granting them access to certain resources or functionalities within a Laravel application. It is a critical aspect of web application security, ensuring that only authorized users can perform specific actions or access sensitive information.

Laravel provides a robust and flexible authentication system out of the box, making it easy to implement standard authentication features such as registration, login, password reset, and email verification. Laravel's authentication features are designed to be simple to implement, while also providing the necessary tools for more complex requirements.

 

Laravel Authentication [Complete Tutorial with examples]

Built-in Authentication System

Also Read: What is Laravel [Explained in Detail]

Laravel comes with pre-built authentication features that can be easily implemented into your application. As of Laravel 5.2 and later versions, these features include:-

  • Authentication Scaffolding: Laravel provides a command (php artisan make:auth) that generates the necessary routes, views, and controllers for registration, login, and password reset functionality. With Laravel 6 and later, this scaffolding is available via Laravel UI, a separate package that can be installed.
  • Users Table: The default authentication system relies on a users table in your database, which Laravel can generate for you using migrations.
  • Eloquent User Model: Laravel uses the User Eloquent model to interact with the users table. You can extend or modify this model to suit your application's needs.

 

Authentication Scaffolding

Authentication scaffolding in Laravel refers to the pre-built authentication features that can be easily implemented into your application, providing a quick way to set up registration, login, and password reset functionalities. Laravel has evolved over time, and the way to generate authentication scaffolding has changed slightly with different versions. Below, I'll explain how to implement authentication scaffolding in Laravel, focusing on Laravel 6.x and later, as Laravel UI, Jetstream, and Breeze have been introduced as options for scaffolding.

 

Laravel UI (For Laravel 6.x and later)

With the release of Laravel 6.x, the frontend scaffolding was moved to a separate package called Laravel UI. To use it for authentication scaffolding, follow these steps:-

 

1. Install Laravel UI

First, you need to install the Laravel UI package using Composer.

composer require laravel/ui

 

2. Generate Login / Registration Scaffolding

After installing Laravel UI, you can generate the basic scaffolding using the php artisan ui command followed by the type of frontend framework you are using (Vue, React, or Bootstrap). For example, to generate scaffolding with Bootstrap.

php artisan ui bootstrap --auth

 

3. Install Frontend Dependencies

Once the scaffolding is generated, install the frontend dependencies and compile your assets using npm or yarn.

npm install && npm run dev

or

yarn && yarn dev

 

 

Laravel Breeze (For Laravel 8.x and later)

Laravel Breeze is a simple implementation of all of Laravel's authentication features, including login, registration, email verification, two-factor authentication, session management, and password confirmation.

 

1. Install Laravel Breeze

Use Composer to install Breeze into your Laravel project.

composer require laravel/breeze --dev

 

2. Install Breeze Scaffolding

After installing the Breeze package, run the breeze:install Artisan command.

php artisan breeze:install

 

3. Run Migrations

Run your database migrations to create the necessary tables for authentication.

php artisan migrate

 

4. Install NPM Dependencies and Compile Assets

To install NPM dependencies and compile assets, run npm install && npm run dev command.

npm install && npm run dev

 

 

Laravel Jetstream (For Laravel 8.x and later)

Laravel Jetstream improves upon Laravel UI and Breeze by offering a more robust starting point for your Laravel application with features like login, registration, email verification, two-factor authentication, session management, API support via Laravel Sanctum, and team management.

 

1. Install Jetstream

Use Composer to install Jetstream into your Laravel project.

composer require laravel/jetstream

 

2. Create Jetstream Scaffolding

Use the jetstream:install Artisan command to install Jetstream with Livewire or Inertia.

For Livewire:

php artisan jetstream:install livewire

For Inertia:

php artisan jetstream:install inertia

 

3. Run Migrations

To run migrations, run php artisan migrate command.

php artisan migrate

 

4. Install NPM Dependencies and Compile Assets

To install npm dependencies and compile assets, run npm install && npm run dev command.

npm install && npm run dev

 

Example

After installing the scaffolding using any of the methods above, your Laravel application will have routes, controllers, and views set up for user authentication. You can customize these files as needed. For instance, the default login route is /login, and the corresponding view file is located at resources/views/auth/login.blade.php. You can modify this Blade template to change the appearance of your login page. Remember, each method (UI, Breeze, Jetstream) has its own set of features and is suited to different types of projects. Choose the one that best fits your project's needs.

 

 

Users Table

In Laravel's authentication system, the users table plays a central role by storing information about the users of the application. This table typically includes essential fields to manage user identification, authentication, and authorization processes. When you set up authentication in a Laravel application, especially using Laravel's built-in features or packages like Laravel Breeze, Laravel Jetstream, or previously Laravel UI, a migration file for the users table is automatically created.

 

Default Structure

The default migration for the users table includes several standard fields. Here's an example of what the migration might look like, which you can find in the database/migrations directory:-

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

Here are the explanation of all the above fields:-

  • id: A unique identifier for each user. Laravel uses an auto-incrementing integer as the default, but you can also use UUIDs or other types of unique identifiers.
  • name: The user's name. This field is often used to store the user's full name or username.
  • email: The user's email address. It's marked as unique to ensure that each user has a distinct email address.
  • email_verified_at: A timestamp indicating when the user's email address was verified. This field supports Laravel's built-in email verification feature and is nullable because not all applications require email verification.
  • password: The user's hashed password. Laravel uses secure Bcrypt hashing by default, ensuring that plain-text passwords are never stored in the database.
  • rememberToken: A token used by Laravel to support the "remember me" functionality, allowing users to remain authenticated between sessions without re-entering their credentials.
  • timestamps(): This method adds two timestamp fields, created_at and updated_at, to track when the user record was created and last updated.

 

Customization

You can customize the users table to suit your application's needs by adding or modifying fields in the migration file before running the migration. For example, you might add fields for a user's profile picture, contact number, address, or any other relevant information.

To add a new field to the users table, update the migration file with a new column definition:-

$table->string('phone')->nullable();

After modifying the migration file, run the migration to update the database schema:-

php artisan migrate

If you need to make changes after the table has already been created, you'll need to create a new migration file specifically for those changes. The users table, with its default structure and the possibility for customization, serves as the foundation for managing user data in Laravel applications, seamlessly integrating with Laravel's authentication system.

 

 

Eloquent User model

In Laravel, the Eloquent User model is a key component of the authentication system. Eloquent is Laravel's ORM (Object-Relational Mapping) tool, which simplifies interactions with the database by presenting tables as classes and rows as objects. The User model, typically found in app/Models/User.php (or app/User.php in older versions of Laravel), represents the users table in the database and is used by Laravel's authentication system to manage user information.

 

Default User Model

When you set up a Laravel project with authentication, a default User model is provided. This model extends the Authenticatable class and implements interfaces like AuthenticatableContract, AuthorizableContract, and CanResetPasswordContract, which define the core user authentication functionalities. Here's a simplified version of what the default User model might look like:-

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
   use HasFactory, Notifiable;

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

   protected $hidden = [
       'password', 'remember_token',
   ];

   protected $casts = [
       'email_verified_at' => 'datetime',
   ];
}

 

Key Components of the User Model

  • Traits: The Notifiable trait enables the user model to send notifications, such as password reset emails. The HasFactory trait is used for database testing and seeding.
  • Fillable Attributes: The $fillable property is an array that specifies which attributes can be mass-assigned, protecting against mass-assignment vulnerabilities.
  • Hidden Attributes: The $hidden array ensures that sensitive information, like passwords and remember tokens, is excluded when the user model is converted to an array or JSON.
  • Casts: The $casts array allows attributes to be cast to common data types. For example, casting the email_verified_at field to datetime ensures it is properly handled as a date-time instance when used in the application.

 

Customization

You can customize the User model to suit your application's requirements. Common customizations include:-

  • Adding Attributes: You can add additional attributes to the $fillable array to allow mass assignment for new columns you've added to the users table.
  • Relationships: Define relationships (e.g., hasMany, belongsTo) within the User model to link users to other models, such as posts, comments, or roles.
  • Accessors and Mutators: Use accessors to format attributes when retrieving them from the database, and mutators to modify attributes before saving them to the database. For example, a mutator could automatically hash passwords before they are stored.
  • Authentication Methods: Override or extend authentication-related methods if you need to customize the authentication logic.

 

Integration with Authentication

The User model is directly integrated with Laravel's authentication system. When users register, log in, or perform other authentication-related actions, Laravel interacts with the users table through the User model. This integration is facilitated by Laravel's authentication services and can be customized or extended to meet specific requirements.

 

 

Configuration

In Laravel, the configuration file for authentication is config/auth.php. This file is central to customizing how your application handles authentication, defining properties such as authentication guards, user providers, passwords, and password reset settings. Understanding the sections and options within this file is crucial for tailoring the authentication system to meet your application's requirements.

 

Key Sections of config/auth.php

a) Defaults

This section specifies the default authentication guard and password reset options for your application. The guard defines how users are authenticated for each request, and Laravel supports web and api guards out of the box.

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

 

b) Guards

Guards define how users are authenticated. The web guard uses session storage and cookies, primarily for traditional web applications. The api guard, on the other hand, is used for stateless API authentication, often using tokens.

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],
],

 

c) Providers

Providers tell Laravel how to retrieve users for authentication. By default, Laravel uses the Eloquent provider to retrieve users using the User model. You can define multiple providers if your application requires authenticating different types of users from different sources.

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],

    // 'users' => [
    // 'driver' => 'database',
    // 'table' => 'users',
    // ],
],

 

d) Passwords

This section defines the settings for password resets, including the password reset table and the email view that should be used for sending password reset links to users.

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
],

 

e) Password Confirmation Timeout

Specifies the number of seconds before a password confirmation times out and the user is prompted to re-enter their password.

'password_timeout' => 10800,

After making changes to the config/auth.php file, ensure that your application is configured to use the correct guards and providers as needed. This might involve adjusting middleware, authentication drivers, or other components of your application's authentication system.

Laravel's flexible authentication configuration allows you to tailor the authentication system to fit a wide range of applications, from simple web applications with traditional login forms to complex APIs requiring token-based authentication.

 

 

Manual Authentication

Laravel provides several methods for manually managing authentication if you need more control than what the built-in features offer. These methods allow you to implement custom authentication logic while still leveraging Laravel's underlying authentication framework. Here's an overview of some common manual authentication methods in Laravel:-

 

Attempting Authentication

To manually authenticate users with their credentials, you can use the attempt method provided by the Auth facade. This method attempts to log in with the given credentials (usually email and password) and optionally can remember the user's session:-

use Illuminate\Support\Facades\Auth;

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// Authentication passed...
return redirect()->intended('dashboard');
}

The intended method redirects the user to the URL they were originally trying to access before being intercepted by the authentication middleware. If no intended URL is found, it redirects to a default path.

 

Checking Authentication

To determine if the current user is authenticated, use the check method:-

use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
// The user is logged in...
}

 

Logging Users In Directly

If you have an instance of the User model that represents the authenticated user, you can log them in directly using the login method:-

use Illuminate\Support\Facades\Auth;

Auth::login($user);

// To remember the user's session:
Auth::login($user, $remember = true);

 

Logging Users Out

To log out the current user and invalidate their session, use the logout method:-

use Illuminate\Support\Facades\Auth;

Auth::logout();

 

Accessing the Authenticated User

To retrieve the authenticated user's instance, use the user method:-

use Illuminate\Support\Facades\Auth;

$user = Auth::user();

 

Validating Credentials Without Login

To validate user credentials without actually logging the user in, use the validate method:-

use Illuminate\Support\Facades\Auth;

if (Auth::validate(['email' => $email, 'password' => $password])) {
// Credentials are valid...
}

 

Manually Logging In Users for a Single Request

You can log in a user for the duration of a single request using the once method. This does not affect session state:-

use Illuminate\Support\Facades\Auth;

if (Auth::once(['email' => $email, 'password' => $password])) {
// User is logged in for this request...
}

 

Using Multiple Authentication Guards

If your application uses multiple guards, you can specify which guard to use for authentication:-

if (Auth::guard('admin')->attempt($credentials)) {
// The admin is authenticated...
}

Laravel's manual authentication methods offer flexibility to implement custom authentication logic while leveraging the framework's robust security features. Whether you need to authenticate users conditionally, log in users without sessions, or integrate with third-party authentication systems, Laravel's manual methods provide the tools you need.

 

 

Password Reset

Laravel provides a straightforward and secure way to implement password reset functionality out of the box. This feature is part of Laravel's authentication system, which helps users regain access to their accounts if they forget their passwords. The process involves sending a password reset link to the user's email, which directs them to a page where they can set a new password.

 

Setting Up Password Reset

a) Database Table: Laravel uses a table (typically named password_resets) to store password reset tokens. You can generate the migration for this table using:-

php artisan make:migration create_password_resets_table

Then, run the migration with php artisan migrate.

b) Routing: Laravel includes routes for displaying the password reset link request form, posting the password reset link email to the user, displaying the password reset form, and resetting the password. These routes are automatically registered when you use Laravel's built-in authentication scaffolding or when you manually define them in your web.php route file.

c) Controllers: Laravel provides ForgotPasswordController and ResetPasswordController within the Auth namespace to handle password reset requests and the reset process. These controllers use traits SendsPasswordResetEmails and ResetsPasswords, respectively, which contain the logic for handling password resets.

d) Views: You need views for the password reset link request form and the password reset form itself. Laravel's authentication scaffolding generates these views for you, or you can create them manually if you're implementing custom logic.

 

The Password Reset Process

  • Request Reset Link: The user accesses the password reset link request form and enters their email address. Laravel sends an email with a unique password reset link to the provided email if it's associated with an account.
  • Reset Email: The email sent to the user contains a link that directs them to the password reset form. Laravel generates this link using a secure, unique token stored in the password_resets table alongside the user's email address.
  • Reset Form: The user clicks the link in their email, which takes them to the password reset form where they can enter a new password. The form includes hidden fields for the email address and the reset token to verify the request.
  • Update Password: Upon submitting the new password, Laravel validates the token, ensures the request's validity, and updates the user's password in the database.

 

Customization

  • Notifications: You can customize the password reset email by overriding the sendPasswordResetNotification method in your User model. This method allows you to specify a custom notification for password resets.
  • Validation: Customize password validation rules in the ResetPasswordController by overriding the rules method.
  • Redirects: You can specify redirect paths after the password reset by defining properties like $redirectTo in your ResetPasswordController.

 

API Authentication

API authentication is a critical aspect of modern web applications, ensuring that only authorized users can access your API endpoints. Laravel provides several options for API authentication, catering to different requirements and preferences. Here are some of the primary methods for implementing API authentication in Laravel:-

 

Token-Based Authentication

Laravel's built-in api guard uses a simple token-based approach for API authentication. Each user is given a unique token, which is sent as part of the request headers to authenticate API calls.

  • Setup: Add the api_token column to your users table and ensure your API routes use the api middleware guard.
  • Usage: Clients must include the token in the request headers (usually as Authorization: Bearer {token}) to access protected routes.

While straightforward, this method doesn't provide advanced features like token expiration, scopes, or token revocation, making it suitable for simpler applications.

 

Laravel Passport

Laravel Passport is a comprehensive OAuth2 server implementation for Laravel, built on top of the League OAuth2 server package. Passport is ideal for more complex API authentication needs, such as third-party app integrations.

  • Features: Passport provides features like token expiration, scopes for limiting token access, and the ability to revoke tokens.
  • Setup: Install Passport via Composer, run the passport:install command to set up encryption keys and client secrets, and then use the Passport::routes method within the AuthServiceProvider.
  • Usage: Passport supports various OAuth2 grant types, including password grant, client credentials grant, and personal access tokens, catering to different use cases like mobile apps, third-party services, or simple personal access tokens for testing.

 

Laravel Sanctum

Laravel Sanctum provides a simpler approach to API token authentication and is particularly well-suited for SPAs (Single Page Applications), mobile applications, and simple token-based API protections.

  • Features: Sanctum supports token-based authentication with the ability to assign abilities/scopes to tokens. It also offers CSRF protection for SPAs that communicate with a Laravel backend.
  • Setup: Install Sanctum via Composer, publish the Sanctum configuration file, and migrate the database to create the necessary tables.
  • Usage: Sanctum allows issuing API tokens with specific abilities and includes middleware for protecting routes. Tokens are typically sent in the request headers to access protected endpoints.

 

JWT (JSON Web Tokens)

While not part of Laravel's core, JSON Web Tokens (JWT) are a popular standard for securely transmitting information between parties as a JSON object. Packages like tymon/jwt-auth provide JWT implementation for Laravel.

  • Features: JWT offers a stateless authentication mechanism, with tokens containing encoded JSON claims that can include user identity, token expiration, and other data.
  • Setup: Install a JWT package like tymon/jwt-auth, configure the secret key, and set up the middleware and guards for your routes.
  • Usage: After successful authentication, the server issues a JWT that the client includes in subsequent requests. The server verifies the token to grant access to protected routes.

Choosing the right API authentication method in Laravel depends on your application's specific requirements. For simpler applications, token-based authentication or Laravel Sanctum might suffice. For applications requiring more robust OAuth2 features, Laravel Passport is a comprehensive solution. Alternatively, JWT can be used for applications favoring the stateless, standard-based approach of JSON Web Tokens. Each method provides a secure and scalable way to handle API authentication in Laravel applications.

 

Routes

Routes in Laravel define URLs for your application and map them to specific controller actions or closure functions. The routes are defined in the routes directory, primarily within the web.php file for web routes and api.php file for API routes.

 

Defining Routes

A basic route might specify a URI, a closure, or point to a controller action:-

// Using a closure
Route::get('/greeting', function () {
    return 'Hello, World!';
});

// Pointing to a controller action
Route::get('/users', 'UserController@index');

 

Route Parameters

Routes can have parameters, making them dynamic. These parameters are passed to the closure or controller action:-

Route::get('/users/{id}', function ($id) {
    return 'User ' . $id;
});

 

Named Routes

Naming routes allows for convenient generation of URLs or redirects:

Route::get('/users/profile', 'UserProfileController@show')->name('profile');

 

Middleware

Middleware can be assigned to routes to perform tasks like authentication before the route is executed:-

Route::get('/dashboard', 'DashboardController@index')->middleware('auth');

 

Grouping Routes

Route groups allow you to share route attributes, such as middleware or namespaces, across many routes:-

Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', 'DashboardController@index');
    // Other authenticated routes...
});

 

Controllers

Controllers organize the request handling logic of your application into classes and methods, making your code more maintainable. They are typically stored in the app/Http/Controllers directory.

 

Basic Controllers

A basic controller might look like this:-

namespace App\Http\Controllers;

use App\Models\User;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    public function index()
    {
        $users = User::all();
        return view('users.index', ['users' => $users]);
    }
}

 

Controller Actions

Controller actions refer to the methods within a controller class that are tied to specific routes in your application. Each action typically corresponds to a particular operation or functionality, such as displaying a view, processing a form submission, or handling an API request. Controller actions organize the logic for handling HTTP requests in a clean, reusable, and maintainable way.

 

Defining Controller Actions

Controller actions are defined as public methods within a controller class. Here's an example of a simple controller with a couple of actions:-

namespace App\Http\Controllers;

use App\Models\User;

class UserController extends Controller
{
    // Action for displaying a list of users
    public function index()
    {
       $users = User::all();
       return view('users.index', ['users' => $users]);
    }

    // Action for displaying a single user details
    public function show($id)
    {
        $user = User::findOrFail($id);
        return view('users.show', ['user' => $user]);
    }
}

 

Routing to Controller Actions

You tie routes to controller actions in your route files, typically located in the routes folder (web.php for web routes, api.php for API routes). Here's how you can define routes that point to the actions in the UserController:-

use App\Http\Controllers\UserController;

// Route for the index action
Route::get('/users', [UserController::class, 'index']);

// Route for the show action with a dynamic parameter
Route::get('/users/{id}', [UserController::class, 'show']);

 

Resource Controllers

For controllers that handle all HTTP requests for a resource (following the RESTful convention), you can generate a resource controller that includes methods like index, create, store, show, edit, update, and destroy. Laravel provides a single route method to handle all these actions:-

php artisan make:controller PhotoController --resource

And you can route to this controller with:-

Route::resource('photos', PhotoController::class);

This automatically sets up routing for various actions like displaying all photos, showing a form to create a new photo, storing a new photo, etc. Controller actions can accept parameters, which are often passed through routes. For example, in the show action above, the $id parameter is passed to the method, allowing you to query and return a specific user.

Controller actions return a response to the browser or calling client. This can be a view, a redirect, a JSON response, or any other type of HTTP response. Laravel automatically converts various return types (like arrays or Eloquent models) to proper JSON responses when needed, particularly in API development.

 

Dependency Injection

Controllers support dependency injection, allowing you to type-hint dependencies in the controller's constructor or methods, which Laravel automatically resolves:-

public function __construct(UserRepository $users)
{
    $this->users = $users;
}

 

Middleware

Middleware can be assigned to controller actions or the entire controller in the constructor:-

$this->middleware('auth')->only('index');

 

Social Authentication

Social authentication in Laravel allows users to log in to your application using their social media accounts, such as Facebook, Google, Twitter, or LinkedIn. This feature enhances the user experience by providing a convenient and fast registration and login process, eliminating the need for users to remember another set of credentials. Laravel Socialite is the official package provided by Laravel for implementing social authentication. It simplifies the OAuth authentication flow with various social media services.

 

Setting Up Laravel Socialite

a) Install Socialite: First, you need to install the Socialite package via Composer:-

composer require laravel/socialite

 

b) Configuration: After installation, you need to configure the social media services you intend to use. This involves:-

  • Registering your application with each social media service to obtain client IDs and secrets.
  • Adding the services to your config/services.php file with their respective client IDs, client secrets, and redirect URIs.
    'google' => [
    'client_id' => env('GOOGLE_CLIENT_ID'),
    'client_secret' => env('GOOGLE_CLIENT_SECRET'),
    'redirect' => env('GOOGLE_REDIRECT_URI'),
],

 

c) Routing: Define routes for redirecting to the social service and handling the callback:-

Route::get('/login/google', 'Auth\LoginController@redirectToGoogle');
Route::get('/login/google/callback', 'Auth\LoginController@handleGoogleCallback');

 

d) Controller: In your controller, implement methods to handle the redirection and callback:-

use Laravel\Socialite\Facades\Socialite;

public function redirectToGoogle()
{
        return Socialite::driver('google')->redirect();
}

public function handleGoogleCallback()
{
       $user = Socialite::driver('google')->user();

      // Use $user information to log in or register the user in your application
}

When a user logs in using a social media service, Socialite provides you with the user's information from that service. You can use this information to log the user in or create a new account in your application. It's common to check if a user with the given social ID or email already exists in your database and log them in, or create a new user account if they don't exist.

 

Security Considerations

  • Validating Emails: Some social media services may not verify users' email addresses. Ensure you handle such cases appropriately, especially if your application's functionality relies on verified email addresses.
  • Multiple Social Accounts: Users might use different social media services to log in to your application. Consider linking multiple social accounts to a single user account in your application.

 

Customizing Socialite

While Socialite supports several popular social media services out of the box, you might need to integrate with a service that isn't supported by default. Socialite allows for custom Socialite providers, enabling you to extend its functionality to support additional services.

Leave a Comment