Skip to content

Latest commit

 

History

History
295 lines (228 loc) · 12.5 KB

platform.md

File metadata and controls

295 lines (228 loc) · 12.5 KB

LTI Service - Platform

How to use the bundle to make your application act as a platform in the context of LTI services.

Table of contents

Providing platform service access token endpoint

The OAuth2AccessTokenCreationAction is automatically added to your application via the related flex recipe, in file config/routes/lti1p3.yaml.

Default route: [POST] '/lti1p3/auth/{keyChainIdentifier}/token'

This endpoint:

  • allow tools to get granted to call your platform services endpoints, by following the client_credentials grant type with assertion.
  • is working for a defined keyChainIdentifier as explained here, so you can expose several of them if your application is acting as several deployed platforms
  • is able to grant (give access tokens) for a defined list of allowed scopes

You must first configure the list of allowed scopes to grant access tokens:

# config/packages/lti1p3.yaml
lti1p3:
    scopes:
        - 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'
        - 'https://purl.imsglobal.org/spec/lti-ags/scope/result/read'

Then, if you configure a key chain as following:

# config/packages/lti1p3.yaml
lti1p3:
    key_chains:
        platformKey:
            key_set_name: "platformSet"
            public_key: "file://path/to/public.key"
            private_key: "file://path/to/private.key"
            private_key_passphrase: 'someSecretPassPhrase'

You can then configure a platform as following (using the key chain identifier platformKey):

# config/packages/lti1p3.yaml
lti1p3:
    platforms:
        myPlatform:
            name: "My Platform"
            audience: "http://platform.com"
            oidc_authentication_url: "http://platform.com/lti1p3/oidc/authentication"
            oauth2_access_token_url: "http://platform.com/lti1p3/auth/platformKey/token"

Once set up, tools can request access tokens by following the client_credentials grant type with assertion:

  • grant_type: client_credentials
  • client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • client_assertion: the tool's generated JWT assertion
  • scope: https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/result/read

Request example:

POST /lti1p3/auth/platformKey/token HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJ0eXAiOi....
&scope=http%3A%2F%2Fimsglobal.org%2Fspec%2Flti-ags%2Fscope%2Flineitem%20http%3A%2F%2Fimsglobal.org%2Fspec%2Flti-ags%2Fscope%2Fresult%2Fread 

As a response, the OAuth2AccessTokenCreationAction will offer an access token (following OAuth2 standards), valid for 3600 seconds:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "access_token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1N.....",
    "token_type" : "bearer",
    "expires_in" : 3600,
    "scope" : "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/result/read"    
} 

Notes:

  • a HTTP 400 response is returned if the requested scopes are not configured, or invalid
  • a HTTP 401 response is returned if the client assertion cannot match a registered tool
  • to automate (and cache) authentication grants from the tools side, a LtiServiceClient is ready to use for your LTI service calls as explained here

Protecting platform service endpoints

For example, considering you have the following platform service endpoints:

#config/routes.yaml
platform_service_ags_lineitem:
    path: /platform/service/ags/lineitem
    controller: App\Action\Platform\Service\Ags\LineItemAction
platform_service_ags_result:
    path: /platform/service/ags/result
    controller: App\Action\Platform\Service\Ags\ResultAction

To protect your endpoint, this bundle provides the lti1p3_service security firewall to put in front of your routes:

# config/packages/security.yaml
security:
    firewalls:
        secured_service_ags_lineitem_area:
            pattern: ^/platform/service/ags/lineitem
            stateless: true
            lti1p3_service: { scopes: ['https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'] }
        secured_service_ags_result_area:
            pattern: ^/platform/service/ags/result
            stateless: true
            lti1p3_service: { scopes: ['https://purl.imsglobal.org/spec/lti-ags/scope/result/read'] }

Note: you can define per firewall the list of allowed scopes, to have better granularity for your endpoints protection.

It will:

  • handle the provided access token validation (signature validity, expiry, matching configured firewall scopes, etc ...)
  • add on success a LtiServiceSecurityToken in the security token storage, that you can use to retrieve your authentication context

For example (in one of the endpoints):

<?php

declare(strict_types=1);

namespace App\Action\Platform\Service\Ags;

use OAT\Bundle\Lti1p3Bundle\Security\Authentication\Token\Service\LtiServiceSecurityToken;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;

class LineItemAction
{
    /** @var Security */
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function __invoke(Request $request): Response
    {
        /** @var LtiServiceSecurityToken $token */
        $token = $this->security->getToken();

        // Related registration (to spare queries)
        $registration = $token->getRegistration();

        // Related access token
        $token = $token->getAccessToken();

        // Related scopes (if you want to implement some ACL)
        $scopes = $token->getScopes(); // ['https://purl.imsglobal.org/spec/lti-ags/scope/lineitem']

        // You can even access validation results
        $validationResults = $token->getValidationResult();

        // Your service endpoint logic ...

        return new Response(...);
    }
}

Providing platform service endpoints using the LTI libraries

We provide a collection of LTI libraries to offer LTI capabilities (NRPS, AGS, basic outcomes, etc) to your application.

The bundle provides a way to easily integrate them when it comes to expose LTI services endpoints:

For example, let's implement step by step the NRPS library membership service endpoint into your application:

  • install the library
$ composer require oat-sa/lib-lti1p3-nrps
  • allow NRPS scope
# config/packages/lti1p3.yaml
lti1p3:
    scopes:
        - 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'
<?php

declare(strict_types=1);

namespace App\Nrps;

use OAT\Library\Lti1p3Core\Registration\RegistrationInterface;
use OAT\Library\Lti1p3Nrps\Model\Membership\MembershipInterface;
use OAT\Library\Lti1p3Nrps\Service\Server\Builder\MembershipServiceServerBuilderInterface;

class MembershipServiceServerBuilder implements MembershipServiceServerBuilderInterface 
{
    public function buildContextMembership(
        RegistrationInterface $registration,
        ?string $role = null,
        ?int $limit = null,
        ?int $offset = null
    ): MembershipInterface {
        // Logic for building context membership for a given registration
    }

    public function buildResourceLinkMembership(
        RegistrationInterface $registration,
        string $resourceLinkIdentifier,
        ?string $role = null,
        ?int $limit = null,
        ?int $offset = null
    ): MembershipInterface {
        // Logic for building resource link membership for a given registration and resource link identifier
    }
};
# config/services.yaml
services:
    OAT\Library\Lti1p3Nrps\Service\Server\Handler\MembershipServiceServerRequestHandler:
        arguments:
            - '@App\Nrps\MembershipServiceServerBuilder'
# config/services.yaml
services:
    app.nrps_membership_controller:
        class: OAT\Bundle\Lti1p3Bundle\Service\Server\Handler\LtiServiceServerHttpFoundationRequestHandler
        factory: ['@OAT\Bundle\Lti1p3Bundle\Service\Server\Factory\LtiServiceServerHttpFoundationRequestHandlerFactoryInterface', 'create']
        arguments:
            - '@OAT\Library\Lti1p3Nrps\Service\Server\Handler\MembershipServiceServerRequestHandler'
        tags: ['controller.service_arguments']
  • bind this controller service to a route in your application
# config/routes.yaml
nrps_membership:
    path: /platform/service/nrps
    controller: app.nrps_membership_controller
# config/packages/security.yaml
security:
    firewalls:
        secured_service_nrps_area:
            pattern: ^/platform/service/nrps
            stateless: true
            lti1p3_service: { scopes: ['https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'] }
  • at this point, your application now offers a new endpoint [GET] /platform/service/nrps, that automates:
    • HTTP method validation
    • NRPS content type validation
    • access token validation
    • access token NRPS scope validation
    • the response of NRPS memberships representations, relying on the provided membership builder implementation

Note: exposing a controller as service is convenient but not mandatory, you can still inject the LtiServiceServerHttpFoundationRequestHandlerFactoryInterface in a controller constructor to have more control on this process, as done in the bundle TestServiceAction for example.