Codeigniter 4 Shield: How to issue an access token

1.2k views Asked by At

I'm starting to work with Codeigniter 4 Shield.

I added this piece of code to my app/Config/Routes.php file.

$routes->get('/access/token', static function() {
    $token = auth()->user()->generateAccessToken(service('request')->getVar('token_name'));

    return json_encode(['token' => $token->raw_token]);
});

When I try to access the route in my web browser using the URL https://example.com/access/token, I obtain the error:

Call to a member function generateAccessToken() on null

produced by the line of code below:

$token = auth()->user()->generateAccessToken(service('request')->getVar('token_name'));

Background information:

  • I have installed Codeigniter 4 Shield using Composer, ran the respective database migrations, and everything else works fine.
  • My Codeigniter 4 Shield 'login' and 'register' pages work fine.

How can I load generateAccessToken() automatically in the app/Config/Routes.php file?

1

There are 1 answers

0
steven7mwesigwa On

You need to submit the login credentials (email & password) along with your HTTP POST request to help identify the User requesting the access token. Otherwise, auth()->user() is empty, hence the error.

To generate an access token, you need to first authenticate the User.

For example: (Using email & password)

  1. Define your 'access token' route. Notice the use of ->post(...) instead of ->get(...).

File: app/Config/Routes.php

$routes->post('auth/token', '\App\Controllers\Auth\LoginController::accessToken');
  1. Define your Controller method that will handle the 'access token' generation. Read: Issuing the Tokens

File: app/Controllers/Auth/LoginController.php

<?php

namespace App\Controllers\Auth;

use App\Controllers\BaseController;

class LoginController extends BaseController
{
    public function accessToken()
    {
        // Validate credentials
        $rules = [
            'email' => [
                'label' => 'Auth.email',
                'rules' => config('AuthSession')->emailValidationRules,
            ],
            'password' => [
                'label' => 'Auth.password',
                'rules' => 'required',
            ],
        ];

        if (!$this->validate($rules)) {
            return $this->response
                ->setJSON(['errors' => $this->validator->getErrors()])
                ->setStatusCode(422);
        }

        if (auth()->loggedIn()) {
            auth()->logout();
        }

        // Attempt to login
        $result = auth()->attempt([
            'email' => $this->request->getPost('email'),
            'password' => $this->request->getPost('password')
        ]);
        if (!$result->isOK()) {
            return $this->response
                ->setJSON(['error' => $result->reason()])
                ->setStatusCode(401);
        }
        // Generate token and return to client
        $token = auth()->user()->generateAccessToken($this->getDeviceName());

        return $this->response
            ->setJSON(['token' => $token->raw_token]);
    }


    public function getDeviceName()
    {
        $agent = $this->request->getUserAgent();

        if ($agent->isBrowser()) {
            $currentAgent = $agent->getBrowser() . ' ' . $agent->getVersion();
        } elseif ($agent->isRobot()) {
            $currentAgent = $agent->getRobot();
        } elseif ($agent->isMobile()) {
            $currentAgent = $agent->getMobile();
        } else {
            $currentAgent = 'Unidentified User Agent';
        }


        return $agent->getPlatform() . " - " . $currentAgent;
    }
}

  1. Protect your /api routes using the $filters setting on app/Config/Filters.php. Read: Protecting Routes
  • Exclude your 'access token' ("auth/token") route together with all API routes ("api/*") from the global "session" & "toolbar" filters.

File: app/Config/Filters.php

<?php

// ...

class Filters extends BaseConfig
{
    // ...

    public array $globals = [
        'before' => [
            'session' => ['except' => [
                "login*",
                "register",
                "auth/a/*",
                "auth/token",
                "api/*"
            ]],
        ],
        'after' => [
            'toolbar' => ['except' => ["auth/token", "api/*"]],
        ],
    ];

    // ...

    public array $filters = [
        'tokens' => ['before' => ["api/*"]],
    ];
}
  1. Make a one-time initial HTTP POST request to the auth/token route to receive the 'access token'. Upon receiving the token, store it with the client. I.e: in localStorage
$.ajax({
    url: "https://your-site-domain.com/auth/token",
    type: "POST",
    data: {
        "email": "USER-EMAIL-ADDRESS-HERE",
        "password": "USER-PASSWORD-HERE",
    },
    success: function (response) {
        window.localStorage.setItem('token', response.token);
    },
    error: function (jqXHR) {
        console.log(jqXHR.responseText);
    },
});
  1. You may now send the received/stored access token using the Authorization header along with all your other protected API HTTP requests in your application without reauthenticating the user. i.e:
$.ajax({
    url: "https://your-site-domain.com/api/rest/v1/employees",
    type: "GET",
    beforeSend: function (jqXHR) {
        jqXHR.setRequestHeader(
            "Authorization",
            "Bearer " + window.localStorage.getItem('token')
        );
    },
    data: {},
    success: function (response) {
        // Use the response here on success.
        // I.e: listing all employees in a table.
    },
    error: function (jqXHR) {
        console.log(jqXHR.responseText);
    },
});