Now Reading
Using Sanctum for API login in Laravel PHP
Dark Light

Using Sanctum for API login in Laravel PHP

laravel auth with sanctum

How many are there still figuring out the right way to implement token based authentication in Laravel using API. There is a package named JWT-Auth which is good for that but Laravel actually has a first party package to do the same.

Sanctum actually become popular with the name issue at first. It was named as Airlock and some company has already claimed the name. So instead of bragging into any issues because of this Taylor just changed the name from Airlock to Sanctum.

I have added Sanctum to my Laravel API. I am adding the steps here so that you can a clear idea of implementing it.

Starting with the Controller. I have made a custom controller for this. Before these make sure you have installed the package already.

composer require laravel/sanctum

Also for publishing the assets that comes with the package and also run the migration that comes with it.

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Now you have to update the middleware to setup authentication in API.

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

Finally we have to add the Trait in the User Model so that it can manage the tokens.

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

With this all the basic steps to setup Sanctum is complete. Now just directly create a custom controller for handling Sanctum Authentication

php artisan make:controller AuthController

Now all the methods for handling login, register scripts to be added in the controller

class AuthController extends Controller
{
    /**
     * Login functionality via API using Laravel
     * Sanctum
     * @param Request $request
     * @return JsonResponse
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'email|required',
            'password' => 'required',
            'device_name' => 'required'
        ]);

        $credentials = request(['email', 'password']);

        if (!Auth::attempt($credentials)) {
            return response()->json([
                'status_code' => 403,
                'message' => 'Unauthorized access'
            ]);
        }

        $user = User::where('email', $request->email)->first();


        if (!$user) {
            return response()->json([
                'status_code' => 403,
                'Message' => 'User not found. Please check your email',
            ], 403);
        }

        if (!Hash::check($request->password, $user->password, [])) {
            return response()->json([
                'status_code' => 403,
                'Message' => 'Please check your email or password',
            ], 403);
        }

        $token = $user->createToken($request->device_name)->plainTextToken;

        return response()->json([
            'status_code' => 200,
            'message' => 'Auth Successful',
            'user' => $user,
            'token' => $token
        ]);
    }

    public function register(Request $request)
    {
        $request->validate([
            'email' => 'email|required|unique:users|string',
            'first_name' => 'string|required|min:3|max:20',
            'last_name' => 'string|required|max:20',
            'password' => 'required|string',
            'device_name' => 'required|string'
        ]);

        $user = User::create([
            'email' => $request->get('email'),
            'first_name' => $request->get('first_name'),
            'last_name' => $request->get('last_name'),
            'password' => Hash::make($request->get('password'))
        ]);

        $token = $user->createToken($request->device_name)->plainTextToken;

        return response()->json([
            'status_code' => 200,
            'message' => 'Auth Successful',
            'user' => $user,
            'token' => $token
        ], 200);

    }

    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();
        return response()->json([
            'status_code' => 200,
            'message' => 'Logout successful',
        ]);
    }
}

In the previous code you can see that I have used an extra request element device_name. This will help you have multiple tokens for different devices with the same login credentials.

Now all the routes to handle the methods in the controller. Update the api.php in the routes folder to handle the requests.

See Also

Route::post('login', 'AuthController@login')->name('api.login');
Route::post('register', 'AuthController@register')->name('api.register');
Route::group(['middleware' => 'auth:sanctum'], function () {
    Route::post('logout', 'Auth\AuthController@logout')->name('api.logout');
});

With these the complete setup of login using sanctum is complete. Remember Sanctum does not comes with JWT-Token. So you should have an endpoint to get the user data or add the same in the login or register end point like how I have added.

Test Cases:

If you are the one who write test cases for your application then I have also adding the test cases for that here. I have created a Unit Test named LoginTest. Here comes the code.

class LoginTest extends TestCase
{

    use RefreshDatabase;

    /**
     * A basic unit test example.
     *
     * @return void
     */

    protected function setUp(): void
    {
        parent::setUp();
    }

    public function test_on_login_returns_json_token()
    {
        $user = factory(User::class)->create();

        $response = $this->post('/api/login', [
            'email' => $user->email,
            'password' => 'password',
            'device_name' => 'ios'
        ]);

        $response->assertStatus(200)
            ->assertJsonFragment([
                'status_code' => 200,
                'message' => 'Auth Successful',
            ]);

        $this->assertAuthenticatedAs($user);
    }

    public function test_on_register_returns_user_and_token()
    {
        $response = $this->post('/api/register', [
            'first_name' => 'Karthick',
            'last_name' => 'K',
            'email' => 'forum@gigcodes.com',
            'password' => 'password',
            'device_name' => 'ios'
        ]);

        $response->assertStatus(200)
            ->assertJsonFragment([
                'status_code' => 200,
                'message' => 'Auth Successful',
            ]);

    }

    public function test_on_repeated_email_produces_error()
    {
        $user = factory(User::class)->create();

        $response = $this->post('/api/register', [
            'first_name' => 'Karthick',
            'last_name' => 'K',
            'email' => $user->email,
            'password' => 'password',
            'device_name' => 'ios'
        ]);

        $response->assertStatus(302)
            ->assertSessionHasErrors('email');
    }

    public function testDoesNotLoginAnInvalidUser()
    {
        $user = factory(User::class)->create();

        $response = $this->post('/api/login', [
            'email' => $user->email,
            'password' => 'invalid',
            'device_name' => 'ios'
        ]);

        $this->assertGuest();
    }

    public function testLogoutAnAuthenticatedUser()
    {
        Sanctum::actingAs(factory(User::class)->create());

        $response = $this->post('/api/logout');

        $response->assertStatus(200)
            ->assertJson([
                'status_code' => 200,
                'message' => 'Logout successful']);
    }
}

Thats it Authentication is complete. If you like it add a comment about it below. If you have some queries please add that below too. We have a forum to share you queries too.

Happy Coding

#stayhome #staysafe #devkarti

View Comments (0)

Leave a Reply

Your email address will not be published.

Scroll To Top