Skip to content

Laravel Implementation Guide

Davo edited this page Nov 9, 2022 · 15 revisions

Installation

Install the library with composer. Open up the AppServiceProvider and set the JWT::$leeway boot() method. (Both of these steps are described in the installation section above).

In the register() method, bind your implementation of the data Cache, Cookie, and Database to their interfaces:

use App\Lti13Cache;
use App\Lti13Cookie;
use App\Lti13Database;
use Firebase\JWT\JWT;
use GuzzleHttp\Client;
use Illuminate\Support\ServiceProvider;
use Packback\Lti1p3\Interfaces\ICache;
use Packback\Lti1p3\Interfaces\ICookie;
use Packback\Lti1p3\Interfaces\IDatabase;
use Packback\Lti1p3\Interfaces\ILtiServiceConnector;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        JWT::$leeway = 5;
    }

    public function register()
    {
        $this->app->bind(ICache::class, Lti13Cache::class);
        $this->app->bind(ICookie::class, Lti13Cookie::class);
        $this->app->bind(IDatabase::class, Lti13Database::class);
        // As of version 3.0
        $this->app->bind(ILtiServiceConnector::class, function () {
            return new LtiServiceConnector(app(ICache::class), new Client([
                'timeout' => 30,
            ]));
        });
    }
}

Once this is done, you can begin building the necessary endpoints to handle an LTI 1.3 launch, such as:

  • Login
  • Launch
  • JWKs

Sample Data Store Implementations

Below are examples of how to get the library's data store interfaces to work with Laravel's facades.

Cache

use Packback\Lti1p3\Interfaces\ICache;

class Lti13Cache implements ICache
{
    public const NONCE_PREFIX = 'nonce_';

    public function getLaunchData(string $key): ?array
    {
        return Cache::get($key);
    }

    public function cacheLaunchData(string $key, array $jwtBody): void
    {
        $duration = Config::get('cache.duration.default');
        Cache::put($key, $jwtBody, $duration);
    }

    public function cacheNonce(string $nonce, string $state): void
    {
        $duration = Config::get('cache.duration.default');
        Cache::put(static::NONCE_PREFIX.$nonce, $state, $duration);
    }

    public function checkNonceIsValid(string $nonce, string $state): bool
    {
        return Cache::get(static::NONCE_PREFIX.$nonce, false) === $state;
    }

    public function cacheAccessToken(string $key, string $accessToken): void
    {
        $duration = Config::get('cache.duration.min');
        Cache::put($key, $accessToken, $duration);
    }

    public function getAccessToken(string $key): ?string
    {
        return Cache::has($key) ? Cache::get($key) : null;
    }

    public function clearAccessToken(string $key): void
    {
        Cache::forget($key);
    }
}

Cookie

use Illuminate\Support\Facades\Cookie;
use Packback\Lti1p3\Interfaces\ICookie;

class Lti13Cookie implements ICookie
{
    public function getCookie(string $name): ?string
    {
        return Cookie::get($name);
    }

    public function setCookie(string $name, string $value, $exp = 3600, $options = []): void
    {
        // By default, make the cookie expire within a minute
        Cookie::queue($name, $value, $exp / 60);
    }
}

Database

To allow for launches to be validated and to allow the tool to know where it has to make calls to, registration data must be stored. For this data store you will need to create models to store the issuer and deployment in the database.

The Packback\Lti1p3\IDatabase interface must be fully implemented for this to work.

use App\Models\Issuer;
use App\Models\Deployment;
use Packback\Lti1p3\Interfaces\IDatabase;
use Packback\Lti1p3\LtiRegistration;
use Packback\Lti1p3\LtiDeployment;
use Packback\Lti1p3\OidcException;

class Lti13Database implements IDatabase
{
    public static function findIssuer($issuer_url, $client_id = null)
    {
        $query = Issuer::where('issuer', $issuer_url);
        if ($client_id) {
            $query = $query->where('client_id', $client_id);
        }
        if ($query->count() > 1) {
            throw new OidcException('Found multiple registrations for the given issuer, ensure a client_id is specified on login (contact your LMS administrator)', 1);
        }
        return $query->first();
    }

    public function findRegistrationByIssuer($issuer, $client_id = null)
    {
        $issuer = self::findIssuer($issuer, $client_id);
        if (!$issuer) {
            return false;
        }

        return LtiRegistration::new()
            ->setAuthTokenUrl($issuer->auth_token_url)
            ->setAuthLoginUrl($issuer->auth_login_url)
            ->setClientId($issuer->client_id)
            ->setKeySetUrl($issuer->key_set_url)
            ->setKid($issuer->kid)
            ->setIssuer($issuer->issuer)
            ->setToolPrivateKey($issuer->tool_private_key);
    }

    public function findDeployment($issuer, $deployment_id, $client_id = null)
    {
        $issuerModel = self::findIssuer($issuer, $client_id);
        if (!$issuerModel) {
            return false;
        }
        $deployment = $issuerModel->deployments()->where('deployment_id', $deployment_id)->first();
        if (!$deployment) {
            return false;
        }

        return LtiDeployment::new()
            ->setDeploymentId($deployment->id);
    }
}

Debugging

To turn on debugging for requests and responses being sent and received from the LtiServiceConnector, in your app set debugging mode to true, e.g. $this->serviceConnector->setDebuggingMode(true);

Clone this wiki locally