longhornopen / laravel-celtic-lti

Integrates the Celtic LTI library with a Laravel app
GNU Lesser General Public License v2.1
11 stars 12 forks source link

Problems with JWT in https #20

Closed BAOOBAP closed 11 months ago

BAOOBAP commented 11 months ago

Good morning,

I have configured a Laravel project that uses your library to connect an external tool to Moodle on my server. With HTTP it works perfectly, the problem comes when I use HTTPS.

I have a Moodle on a server, and the project is local, but I use a domain generated with Ngrok, so far so good. When I open the external tool, it makes a request to the back, and it returns the form to fill in the data, but when it validates the JWT, it shows the following error: "JWT signature check failed - perhaps an invalid public key or timestamp". A LonghornOpenLaravelCelticLTILTILtiException exception. I have seen the content of $_SERVER and it seems that in the REQUEST_SCHEME parameter the protocol used is HTTP, I have manually forced it to be HTTPS, but it still gives the same error. In the .env file I have added the following variables:


APP_ENV=prod

APP_KEY=base64:EUTdkaNilbUlObr3+rfW4TLuh0nsROh51IkKIO5lsdc= # php artisan key:generate

APP_DEBUG=true

APP_URL=back

APP_PROXY=https://6ea4-139-47-20-144.ngrok-free.app

APP_PROXY_PORT=

APP_HTTPS=on

In the boot of the project I force the requests to be HTTPS:


public function boot()

{

    if($this->app->environment('production')) {

        \Illuminate supportFacadesURL::forceScheme('https');

    }

}

I've even added middleware that forces requests to HTTPS (I don't know if the latter are good for anything):


<?php

namespace App\Http middleware;

use Closure;

use IlluminateHttpRequest;

class ForceHttps

{

    /**

     * Handle an incoming request.

     *

     * @ @param \IlluminateHttp\Request $request

     * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|Illuminate\Http\RedirectResponse) $next

     * @return \Illuminate\HttpRedirectResponse|Illuminate\HttpRedirectResponse

     */

    public function handle($request, Closure $next)

    {

        if (!$request->secure() && env('APP_ENV') === 'prod') {

            error_log('login');

            $request->server->set('HTTPS', true);

        }

        error_log(print_r($request, true));

        return $next($request);

    }

}

Is there anything I'm missing or not taking into account?

Thanks!

BAOOBAP commented 11 months ago

I forgot to mention that I am using version 0.4.9.

chrispittman commented 11 months ago

When you set up an LTI tool in an LMS, you have to give it a few URLs provided by the tool. This sounds like Moodle is making calls to HTTP URLs... have you tried updating your configuration in the LMS to use HTTPS URLs instead?

BAOOBAP commented 11 months ago

All configured urls are https:

URL of the tool: https://1908-139-47-20-144.ngrok-free.app/back/lti

Public key set: https://1908-139-47-20-144.ngrok-free.app/back/lti/jwks

Launch login URL: https://1908-139-47-20-144.ngrok-free.app/back/lti

Redirect URI(s): https://1908-139-47-20-144.ngrok-free.app/back/lti

and I can see the contents of the path https://1908-139-47-20-144.ngrok-free.app/back/lti/jwks :


{"keys":[{"kty":"RSA","n": "yM7mK91z0OvMoXhlNA_51_qI0SyFhd- of0wEtFOBypqSaezGdjTQOfvecvecqpcm2m1s7EwKmA1vdb78DRZTQQVbcypXYvAyqyCCa9KhzeYJsMVixi4Sv5m0kRvPNVPf0WNknC25f8jbskkAwLnosl7P4eUX9xtYSZcbkUPdwoxYQQEaUUsFM9t_PzICtZ6VNlSEO2Yh1tjfVKDm8QQFAxocyHaKBWRiqqqmH- 6eao9Fcu- aH4H44BNLJt48SQssWzAypuuv6mUtuhPuauiJX86mdjTPG1Jxzfn13scK-eq72JhQmRNwzQT1bJM_80gpqqNnV8ZDDVXpsXVmRE- N8j_GXmiAVXLTnpl3bmtXbgSJfrGl1RBYl8vuI4lLLblsFtYu7PcP1pi7QX0j1pbIGpPW2g5Tp0L88z389EdcOoD5dl6pzxBLFVhhtRMCC6bLlk22Zx4NSbzMYRlvcyw2_vzJplvqsAzt9udrB5a3Luigkhy292P2rGQxvtzaXS468TMUFrd", "e": "AQAB","alg":"RS256","use":"sig","kid":"XYZ"}]} ```
chrispittman commented 11 months ago

If I'm understanding you right, the JWK that it's complaining about isn't the one that's provided by your app, it's the one provided by the LMS. For Moodle, that's at the URL /mod/lti/certs.php. Check that URL, and make sure that it's accessible and returns an actual JWKS when accessed.

BAOOBAP commented 11 months ago

It is accessible and returns this:

'''

{

"keys": [

    {

        "kty": "RSA",

        "alg": "RS256",

        "kid": "591f1fac32959533c640",

        "e": "AQAB",

        "n": "vJXnGM8ECSlUYYYZ3pjVDrywyMlkQCF10fr2oR86XRA3DjcDmukTXmiWJHi3pFcIm27ILf0jW3_PFzxhNfkBYUoD3AbxxnEE-7LAuWbVI_ReVq6364TY2SEB2OMdCM_RO9FLcK7CfsqIvzF_tO0NNNQ_sDXvVV-". WyrlkwLVx8oMnD8y2RMpcNlFRTPOBWKyqifEAEYySXTSvU4FxodmalH9Vk0F_6_jP61an012rr1AszqVIqYZnQTtEDHSaCPeKKK0M4Yporoi4rvL16e8LJh5dh1qTSuBu9uT2ryYbA70SozwdWcSdxhFYDE6kmsoKv9c20XJM9KD7gDudonjKs5iPkw",

        "use": "sig"

    }

]

}

'''

BAOOBAP commented 11 months ago

Sorry for not explaining it correctly.

The LMS is hosted on a server with ssl certificate, and the project is installed on a wamp server on a local machine, but for https requests, I redirect to a domain generated by ngrok, the case is that this was all mounted locally and gave the same error that I'm having with the jwt:


JWT signature check failed - perhaps an invalid public key or timestamp

Is it possible that the locally generated certificate could be the problem?

It was generated with openssl, maybe because it is autogenerated, it is not working.

chrispittman commented 11 months ago

I run my development servers with a self-signed HTTPS certificate too, and it hasn't been a problem (other than needing to tell my browser to trust the cert).

You've generated an RSA public/private keypair (as described in https://github.com/longhornopen/laravel-celtic-lti/wiki/Laravel-app-setup), and edited your config/lti.php to tell your app where to find them, correct?

BAOOBAP commented 11 months ago

Yes, I have created the RSA keys, and they have been added to the project to the config/lti.php file:


'rsa_public_key' => file_get_contents(storage_path('keys/public.pem')),
'rsa_private_key' => file_get_contents(storage_path('keys/private.pem')),

Is it possible that I should also add the certificate created with these keys to the wamp server?

chrispittman commented 11 months ago

No, these keys aren't used for securing HTTPS, only for generating and validating JWTs.

Not sure what to tell you at this point. If you've generated proper keys, the private key should start with '-----BEGIN RSA PRIVATE KEY-----' and the public key should begin with '-----BEGIN PUBLIC KEY----'. If those are where config/lti.php says they are, everything should work fine.

Version 1.0.0 bumps us up to a newer version of the Firebase JWT library - maybe try updating and see if that helps?

BAOOBAP commented 11 months ago

I have confirmed that the headers are inside the files. I have also upgraded to the latest version of the library, but it still gives the same error.

chrispittman commented 11 months ago

In your database, the lti2_consumer table describes the LMSes you're connecting to. There should be a row in there for your Moodle instance. What's in the 'settings' column?

BAOOBAP commented 11 months ago

This is the content of settings:

{"_authentication_request_url": "https:\/\/moodle.silta-dixit.com/mod\/lti\/auth.php","_oauth2_access_token_url": "https:\/\/moodle.silta-dixit.com/mod\/lti\/token.php","_jku": "https:\/\/moodle.silta-dixit.com/mod\/lti\/certs.php"}

Testing, I removed the part where the jwt is validated, and the communication is successful, I can get the data returned by the LMS, and if I make the request configuring both the external tool of the LMS and the configuration of the back with the http protocol, I have no problem.

BAOOBAP commented 11 months ago

Sorry, I updated a test back that I did not point to, it has changed the error it returned, now it returns this one:


str_replace(): Argument #3 ($subject) must be of type array|string, bool given

In function $tool->handle_request()

BAOOBAP commented 11 months ago

I have done a var_dump of the $_SERVER variable and this is what it returns:

'HTTP_HOST' => string '7002-139-47-20-144.ngrok-free.app' (length=33)
  'HTTP_USER_AGENT' => string 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' (length=111)
  'CONTENT_LENGTH' => string '275' (length=3)
  'HTTP_ACCEPT' => string 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' (length=135)
  'HTTP_ACCEPT_ENCODING' => string 'gzip, deflate, br' (length=17)
  'HTTP_ACCEPT_LANGUAGE' => string 'es-ES,es;q=0.9,en;q=0.8' (length=23)
  'HTTP_CACHE_CONTROL' => string 'max-age=0' (length=9)
  'CONTENT_TYPE' => string 'application/x-www-form-urlencoded' (length=33)
  'HTTP_COOKIE' => string 'abuse_interstitial=7002-139-47-20-144.ngrok-free.app' (length=52)
  'HTTP_ORIGIN' => string 'https://moodle.silta-dixit.com' (length=30)
  'HTTP_REFERER' => string 'https://moodle.silta-dixit.com/' (length=31)
  'HTTP_SEC_CH_UA' => string '"Chromium";v="118", "Microsoft Edge";v="118", "Not=A?Brand";v="99"' (length=66)
  'HTTP_SEC_CH_UA_MOBILE' => string '?0' (length=2)
  'HTTP_SEC_CH_UA_PLATFORM' => string '"Windows"' (length=9)
  'HTTP_SEC_FETCH_DEST' => string 'document' (length=8)
  'HTTP_SEC_FETCH_MODE' => string 'navigate' (length=8)
  'HTTP_SEC_FETCH_SITE' => string 'cross-site' (length=10)
  'HTTP_UPGRADE_INSECURE_REQUESTS' => string '1' (length=1)
  'HTTP_X_FORWARDED_FOR' => string '139.47.20.144' (length=13)
  'HTTP_X_FORWARDED_HOST' => string '7002-139-47-20-144.ngrok-free.app' (length=33)
  'HTTP_X_FORWARDED_PROTO' => string 'https' (length=5)
  'PATH' => string 'C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Python311\Scripts\;C:\Python311\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\PuTTY\;C:\ProgramData\chocolatey\bin;C:\Program Files\nodejs\;C:\Program Files\dotnet\;C:\Program Files\Git\cmd;C:\ProgramData\ComposerSetup\bin;C:\Certbot\bin;C:\opt\j'... (length=664)
  'SystemRoot' => string 'C:\WINDOWS' (length=10)
  'COMSPEC' => string 'C:\WINDOWS\system32\cmd.exe' (length=27)
  'PATHEXT' => string '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW' (length=62)
  'WINDIR' => string 'C:\WINDOWS' (length=10)
  'SERVER_SIGNATURE' => string '<address>Apache/2.4.54 (Win64) OpenSSL/1.1.1s PHP/8.1.13 mod_fcgid/2.3.10-dev Server at 7002-139-47-20-144.ngrok-free.app Port 80</address>
' (length=140)
  'SERVER_SOFTWARE' => string 'Apache/2.4.54 (Win64) OpenSSL/1.1.1s PHP/8.1.13 mod_fcgid/2.3.10-dev' (length=68)
  'SERVER_NAME' => string 'https://6ea4-139-47-20-144.ngrok-free.app' (length=41)
  'SERVER_ADDR' => string '::1' (length=3)
  'SERVER_PORT' => string '' (length=0)
  'REMOTE_ADDR' => string '::1' (length=3)
  'DOCUMENT_ROOT' => string 'C:/wamp64/www' (length=13)
  'REQUEST_SCHEME' => string 'http' (length=4)
  'CONTEXT_PREFIX' => string '/back' (length=5)
  'CONTEXT_DOCUMENT_ROOT' => string 'c:/wamp64/www/Nueva_carpeta/uniadaptiveLTI-Back/public/' (length=55)
  'SERVER_ADMIN' => string 'wampserver@wampserver.invalid' (length=29)
  'SCRIPT_FILENAME' => string 'C:/wamp64/www/Nueva_carpeta/uniadaptiveLTI-Back/public/index.php' (length=64)
  'REMOTE_PORT' => string '56880' (length=5)
  'GATEWAY_INTERFACE' => string 'CGI/1.1' (length=7)
  'SERVER_PROTOCOL' => string 'HTTP/1.1' (length=8)
  'REQUEST_METHOD' => string 'POST' (length=4)
  'QUERY_STRING' => string '' (length=0)
  'REQUEST_URI' => string '/back/index.php/lti' (length=19)
  'SCRIPT_NAME' => string '/back/index.php' (length=15)
  'PATH_INFO' => string '/lti' (length=4)
  'PATH_TRANSLATED' => string 'C:\wamp64\www\lti' (length=17)
  'PHP_SELF' => string '/back/index.php/lti' (length=19)
  'REQUEST_TIME_FLOAT' => float 1701514738.216
  'REQUEST_TIME' => int 1701514738
  'APP_NAME' => string 'uniadaptiveLTI-Back' (length=19)
  'APP_ENV' => string 'prod' (length=4)
  'APP_KEY' => string 'base64:EUTdkaNilbUlObr3+rfW4TLuh0nsROh51IkKIO5lsdc=' (length=51)
  'APP_DEBUG' => string 'true' (length=4)
  'APP_URL' => string 'back' (length=4)
  'APP_PROXY' => string 'https://6ea4-139-47-20-144.ngrok-free.app' (length=41)
  'APP_PROXY_PORT' => string '' (length=0)
  'APP_HTTPS' => string 'on' (length=2)
  'LTI13_SIGNATURE_METHOD' => string 'RS256' (length=5)
  'LTI13_KEY_ID' => string 'XYZ' (length=3)
  'LTI13_AUTO_REGISTER_DEPLOYMENT_ID' => string 'false' (length=5)
  'LOG_CHANNEL' => string 'errorlog' (length=8)
  'LOG_DEPRECATIONS_CHANNEL' => string 'null' (length=4)
  'LOG_LEVEL' => string 'debug' (length=5)
  'DB_CONNECTION' => string 'mysql' (length=5)
  'DB_HOST' => string 'localhost' (length=9)
  'DB_PORT' => string '3306' (length=4)
  'DB_DATABASE' => string 'uniadaptative_back' (length=18)
  'DB_USERNAME' => string 'root' (length=4)
  'DB_PASSWORD' => string '' (length=0)
  'BROADCAST_DRIVER' => string 'log' (length=3)
  'CACHE_DRIVER' => string 'file' (length=4)
  'FILESYSTEM_DISK' => string 'local' (length=5)
  'QUEUE_CONNECTION' => string 'sync' (length=4)
  'SESSION_DRIVER' => string 'file' (length=4)
  'SESSION_LIFETIME' => string '120' (length=3)
  'MEMCACHED_HOST' => string '127.0.0.1' (length=9)
  'REDIS_HOST' => string '127.0.0.1' (length=9)
  'REDIS_PASSWORD' => string 'null' (length=4)
  'REDIS_PORT' => string '6379' (length=4)
  'FRONT_URL' => string 'https://550c-139-47-20-144.ngrok-free.app/front' (length=47)
  'ADMIN_PASSWORD' => string 'admin' (length=5)
  'JWT_SECRET' => string 'JD897Q71UIbSBsNUSvTlKtOFqVXSGG6htu1BJJQEBWIEdzc3Udv1QPZl1RRlEaB5' (length=64))

I have noticed that the 'REQUEST_SCHEME' parameter contains http


'REQUEST_SCHEME' => string 'http'
chrispittman commented 11 months ago

"Request scheme contains http": I've never used ngrok in particular, but that's typical for front-end proxies. The proxy (ngrok, in this case) handles HTTPS connections, and forwards an HTTP connection to the application. If the proxy server is listed in Laravel's TrustProxies middleware, everything gets handled transparently. (I'm assuming that your APP_PROXY env var is involved in setting up your TrustProxies somehow.)

You say you've run this successfully over HTTP. Was that HTTP connection being handled by ngrok also?

BAOOBAP commented 11 months ago

Yes, I have tested the http both locally and being redirected by the ngrok.

The difference is whether moodle uses http or https.

BAOOBAP commented 11 months ago

I have used lti-example-app to test what it returns, and it returns the same error:


JWT signature check failed - perhaps an invalid public key or timestamp

I don't know what else to try

BAOOBAP commented 11 months ago

I found the solution to the problem.

I have installed the WAMP server on Windows. It appears that on Windows, you need to add a cert.pem file to validate the certificate of the server where our Moodle is hosted. To do this, I downloaded the Mozilla CA certificate and added it to the PHP server, which resolved the issue. I extracted the file from the following link: https://curl.se/docs/caextract.html

chrispittman commented 11 months ago

Cool, glad you're up and running again!