-
Notifications
You must be signed in to change notification settings - Fork 25
Laravel Implementation Guide
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;
use Packback\Lti1p3\LtiServiceConnector;
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
Below are examples of how to get the library's data store interfaces to work with Laravel's facades.
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);
}
}
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);
}
}
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);
}
}
This is optional, but because several objects in this library requires the cache, cookie, database and service connector to be instantiated, it may be easier to create a class that handles resolving these from the app container and instantiating objects as needed.
namespace App\Services;
use App\Models\Issuer;
use Illuminate\Http\Request;
use Packback\Lti1p3\Interfaces\ICache;
use Packback\Lti1p3\Interfaces\ICookie;
use Packback\Lti1p3\Interfaces\IDatabase;
use Packback\Lti1p3\Interfaces\ILtiServiceConnector;
use Packback\Lti1p3\JwksEndpoint;
use Packback\Lti1p3\LtiDeepLinkResource;
use Packback\Lti1p3\LtiMessageLaunch;
use Packback\Lti1p3\LtiOidcLogin;
/**
* Class Lti13Service.
*/
class Lti13Service
{
public $db;
public $cache;
public $cookie;
public $serviceConnector;
private $launchUrl;
public function __construct(
IDatabase $db,
ICache $cache,
ICookie $cookie,
ILtiServiceConnector $serviceConnector)
{
$this->db = $db;
$this->cache = $cache;
$this->cookie = $cookie;
$this->serviceConnector = $serviceConnector;
$this->launchUrl = route('lti-launch');
}
/**
* Validate an LTI launch.
*
* @throws Packback\Lti1p3\LtiException
*/
public function validateLaunch(Request $request): LtiMessageLaunch
{
return LtiMessageLaunch::new($this->db, $this->cache, $this->cookie, $this->serviceConnector)
->validate($request->all());
}
/**
* Launch a deep link.
*/
public function launchDeepLink(LtiMessageLaunch $launch): void
{
$resource = LtiDeepLinkResource::new()
->setUrl($this->launchUrl);
$launch->getDeepLink()->outputResponseForm([$resource]);
}
/**
* Get the URL for an OIDC login redirect.
*
* @throws Packback\Lti1p3\OidcException
*/
public function login(Request $request): string
{
return LtiOidcLogin::new($this->db, $this->cache, $this->cookie)
->doOidcLoginRedirect($this->launchUrl, $request->all())
->getRedirectUrl();
}
/**
* Get a JWKS objects (optionally by ID).
*/
public function jwks(string $id = null): array
{
$issuer = Issuer::findOrFail($id);
return JwksEndpoint::new([$issuer->kid => $issuer->tool_private_key])->getPublicJwks();
}
}
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);