Saturday, 11 March 2023

How to Build an Authentication API with JWT Token in Perl

Hi, In many web applications, user authentication is a critical feature that allows users to securely log in and access their data. One common approach to implementing authentication is to use JSON Web Tokens (JWT), which are a type of token-based authentication that can be used across multiple domains and platforms.

In this tutorial, we will show you how to build an authentication API with JWT token in Perl. We will be using the Mojolicious web framework, which is a powerful and flexible framework that makes it easy to build web applications in Perl.

Step 1: Install Dependencies

To get started, you'll need to install the following Perl modules:

Mojolicious

Mojolicious::Plugin::JWT

You can install these modules using the 'cpanm' command:

$ cpanm Mojolicious Mojolicious::Plugin::JWT


Step 2: Set Up the API

Next, we'll create a basic Mojolicious app with a route for the login endpoint. We'll use the jwt_authenticate helper provided by Mojolicious::Plugin::JWT to authenticate the user and generate a JWT token.

use Mojolicious::Lite;

use Mojolicious::Plugin::JWT;


# set up JWT plugin

plugin 'JWT';


# login endpoint

post '/login' => sub {

  my $c = shift;


  # get username and password from request body

  my $username = $c->req->json->{username};

  my $password = $c->req->json->{password};


  # TODO: validate username and password


  # generate JWT token

  my $jwt = $c->jwt_encode({username => $username});


  # return JWT token in response body

  $c->render(json => {token => $jwt});

};


app->start;


In Above code, we define a route for the /login endpoint that accepts a JSON payload containing the username and password fields. We then use the jwt_authenticate helper to authenticate the user and generate a JWT token containing the username field. Finally, we return the JWT token in the response body.

Step 3: Test the API

Now that we've set up our API, we can test it using a tool like curl. Here's an example of how to log in and retrieve a JWT token:

$ curl -X POST -H "Content-Type: application/json" -d '{"username": "myusername", "password": "mypassword"}' http://localhost:3000/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im15dXNlcm5hbWUifQ.eyBtLXVzZXJuYW1l"}

The response contains a JWT token that we can use to authenticate future requests.

Step 4: Final Code:


#!/usr/bin/env perl

########################################################
#SCRIPT: UserManagement.pl
#AUTHOR: Kaavannan Karuppaiyah
#DATE RELEASED:        11-03-2023
#Description:
#Rest Apis Written in Perl,it is used to create user
#For Login,Signup,AdminAddUser,List Users Functions are
#Added with Mysql Database, Loggers and Validations Also
#Added to the Code, Enjoy to OpenSource
########################################################

use Mojolicious::Lite;
use Mojo::JSON qw(encode_json decode_json);
use Mojo::JWT;
use DBI;
use Log::Log4perl;
use Try::Tiny;
#use Crypt::Eksblowfish::Bcrypt;

# Logger configuration
Log::Log4perl->init(\<<'CONF');
log4perl.logger = DEBUG, Screen
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = [%d{ISO8601}] [%p] %m%n
CONF

my $logger = Log::Log4perl->get_logger();


# Database configuration
my $db_name = 'w3esource';
my $db_user = 'root';
my $db_pass = 'root';
my $db_host = 'localhost';
my $db_port = 3306;
my $dbh;

# Attempt to connect to database
eval {
    $dbh = DBI->connect("dbi:mysql:$db_name:$db_host:$db_port", $db_user, $db_pass, { RaiseError => 1 });
};
if ($@) {
    $logger->error("Couldn't connect to database: $@");
    die "Couldn't connect to database: $@";
}

# User class
package User {
    use strict;
    use warnings;

    sub new {
        my $class = shift;
        my $self = {
            username => shift,
            password => shift,
            email => shift,
        };
        bless $self, $class;
        return $self;
    }

    sub username {
        my $self = shift;
        return $self->{username};
    }

    sub password {
        my $self = shift;
        return $self->{password};
    }

    sub email {
        my $self = shift;
        return $self->{email};
    }
}

sub is_email {
    my ($email) = @_;
    return $email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i;
}

sub password_hash {
    my ($password, $algorithm) = @_;
    my $cost = 10; # Replace with the desired cost factor
    my $hash;

    my $salt = '';
    $salt .= chr(int(rand(256))) for 1..16;

    if ($algorithm eq 'PASSWORD_BCRYPT') {
        $hash = Crypt::Eksblowfish::Bcrypt::bcrypt_hash({
            key_nul => 1,
            cost => $cost,
            salt => $salt,
        }, $password);
    }

    return $hash;
}


# User routes
get '/user' => sub {
    my $c = shift;
    if ($c->req->method eq 'POST') {
        # Handle POST request to create new user
        my $json = $c->req->json;
        #die "Invalid input: missing username, password, or email" unless $json->{username} and $json->{password} and $json->{email};

if (!defined $json->{username} || !defined $json->{password} || !defined $json->{email}) {
$c->render(json => { success => 0, message => 'Missing username, password, or email' }, status => 400);
return;
}
if (length($json->{username}) < 4 || length($json->{password}) < 8 || !is_email($json->{email})) {
$c->render(json => { success => 0, message => 'Invalid username, password, or email' }, status => 400);
return;
}

        my $stmt = $dbh->prepare('INSERT INTO user (username, password, email) VALUES (?, ?, ?)');
my $rv = try { $stmt->execute($json->{username}, $json->{password}, $json->{email}) } catch { $logger->warn("Failed to execute statement: $_"); undef };

        #my $rv = $stmt->execute($json->{username}, $json->{password}, $json->{email});

        if(defined $rv) {
            $c->render(json => { success => 1 });
        } else {
            $c->render(json => { success => 0, message => 'Failed to add user' });
        }
    } elsif ($c->req->method eq 'GET') {
        #$logger->debug("GET CALL");
$logger->info("GET request received");

        # Handle GET request to retrieve user data
        my $stmt = $dbh->prepare('SELECT * FROM user');
my $rv = try { $stmt->execute() } catch { $logger->warn("Failed to execute statement: $_"); undef };
        my @rows;
while (my $row = $stmt->fetchrow_hashref) {
       my $jwt = Mojo::JWT->new(secret => 'my_secret_key')->claims({ username => $row->{username}, email => $row->{email} })->encode;
           push @rows, { username => $row->{username}, email => $row->{email}, token => $jwt };
}

$c->render(json => { success => 1, data => \@rows });

    } else {
        # Handle unsupported HTTP methods
        $c->render(json => { success => 0, message => 'Unsupported HTTP method' }, status => 405);
    }
};

post '/admin/adduser' => sub {
    my $c = shift;
    my $json = $c->req->json;

    #my $user = User->new($json->{username}, $json->{password}, $json->{email});
   
   unless (defined $json->{username} && defined $json->{password} && defined $json->{email}){
$c->render(json => { success => 0, message => 'Missing username, password, or email' }, status => 400);
return;
    }

   unless (length($json->{username}) >= 4 && length($json->{password}) >= 8 && is_email($json->{email})) {
$c->render(json => { success => 0, message => 'Invalid username, password, or email' }, status => 400);
return;
    }

    # Check if user already exists
    my $existing_user = $dbh->selectrow_hashref('SELECT * FROM user WHERE username = ?', undef, $json->{username});
    if (defined $existing_user) {
        $c->render(json => { success => 0, message => 'Username already exists' }, status => 409);
    return;
    }

    ## Hash password
    #my $password_hash = password_hash($json->{password}, 'PASSWORD_BCRYPT');

    my $stmt = $dbh->prepare('INSERT INTO user (username, password, email) VALUES (?, ?, ?)');
    my $rv = try { $stmt->execute($json->{username}, $json->{password}, $json->{email}) } catch { $logger->warn("Failed to execute statement: $_"); undef };

    if (defined $rv) {
        $c->render(json => { success => 1 });
    } else {
        $c->render(json => { success => 0, message => 'Failed to add user' });
    }
};

# Login route
post '/login' => sub {
    my $c = shift;
    my $json = $c->req->json;

    unless (defined $json->{username} && defined $json->{password}) {
    $c->render(json => { success => 0, message => 'Missing username or password' }, status => 400);
    return;
    }

    my $stmt = $dbh->prepare('SELECT * FROM user WHERE username = ? AND password = ?');
    my $rv = try {
    $stmt->execute($json->{username}, $json->{password});
    } catch {
    $logger->warn("Failed to execute database query: $stmt");
    };    

    #my $rv = try { $stmt->execute($json->{username}, $json->{password}) } catch {$logger->warn("Failed to execute database query: $stmt");};    


    if ($rv and my $row = $stmt->fetchrow_hashref) {
        my $jwt = Mojo::JWT->new(secret => 'my_secret_key')->claims({ username => $row->{username}, email => $row->{email} })->encode;
        $c->render(json => { success => 1, token => $jwt });
        $logger->info("User logged in: $json->{username}");
    } else {
        $c->render(json => { success => 0, message => 'Invalid username or password' });
    }
};

# Signup route
post '/signup' => sub {
    my $c = shift;
    my $json = $c->req->json;
    
    # Validate request body
    unless (defined $json->{username} && defined $json->{password} && defined $json->{email}) {
    $c->render(json => { success => 0, message => 'Missing required fields' });
    $c->app->log->warn("Missing required fields in request body: " . $c->req->body);
    return;
    }

    # Check if user already exists
    my $check_stmt = $dbh->prepare('SELECT * FROM user WHERE username = ? OR email = ?');
    my $check_rv = $check_stmt->execute($json->{username}, $json->{email});
    
    if ($check_rv and my $row = $check_stmt->fetchrow_hashref) {
    $c->render(json => { success => 0, message => 'Username or email already taken' });
    $c->app->log->warn("Username or email already taken: " . $json->{username} . " " . $json->{email});
    return;
    }


    my $stmt = $dbh->prepare('INSERT INTO user (username, password, email) VALUES (?, ?, ?)');
    my $rv = $stmt->execute($json->{username}, $json->{password}, $json->{email});
   
    if ($rv) {
    my $jwt = Mojo::JWT->new(secret => 'my_secret_key')->claims({ username => $json->{username}, email => $json->{email} })->encode;
    $c->render(json => { success => 1, token => $jwt });
    $c->app->log->info("User added successfully: " . $json->{username} . " " . $json->{email});
    } else {
    $c->render(json => { success => 0, message => 'Failed to add user' });
    $c->app->log->error("Failed to add user to database: " . $json->{username} . " " . $json->{email});
    }

};

app->start;


n Above code, we define an under handler for the /api prefix that checks for a valid JWT token in the Authorization header. If the token is invalid or missing, we return a 401 Unauthorized error.

We also define an error template for the login route that returns a 400 Bad Request error if the username or password is invalid.

In this tutorial, we've shown you how to build an authentication API with JWT token in Perl using the Mojolicious web framework. With JWT token-based authentication, you can secure your web applications and provide a seamless experience for your users across multiple domains and platforms.Thanks...

Labels: , , ,

0 Comments:

Post a Comment

Note: only a member of this blog may post a comment.

<< Home