JWT authentication issues via API

Hi, I’m encountering some difficulties login in using JWT method described here.
Here is the response that i get from the server.

{
    "header": {
        "id": "f99baa93-4d32-4f1e-b489-492818a498e2",
        "status": "error",
        "servertime": 1727007011,
        "action": "28c0972b-e6a2-5d44-a5cb-bc2d11799cc1",
        "message": "Credenziali non valide.",  //Invalid credentials
        "url": "/auth/jwt/login.json",
        "code": 400
    },
    "body": ""
}

I’m using OpenPGP.js and Bun.
This is the code that generates the payload:

  const uuid = crypto.randomUUID();
  const date = new Date();
  date.setMinutes(date.getMinutes() + 30);

  const payload = {
    version: "1.0.0",
    domain: url,
    verify_token: uuid,
    //Token expires in 30 mins
    verify_token_expiry: date.valueOf(),
  };

  const privateKey = await readPrivateKey(passphrase); //reads the private key from a file

  const publicKey = await getPublicKey(url); // get the public key from /auth/verify.json

  // Encrypt the signed payload with the server's public key
  const encryptedPayload = await openpgp
    .encrypt({
      message: await openpgp.createMessage({ text: JSON.stringify(payload) }),
      encryptionKeys: publicKey,
      signingKeys: privateKey,
    })

  return {
    user_id: user_id,
    challenge: encryptedPayload,
  };

If someone could help me, I would be very thankful.

Same problem here… I have spent a good amount of hours without finding a solution. :sweat_smile::sweat_smile:

Passbolt Docker linked to passbolt_api repo. GPGAuth integration was fine, but I’ve seen now that JWT is the way.

Can you guys have a look at this POC: GitHub - nourcy/passbolt-poc-jwt

The challenge is set here: passbolt-poc-jwt/src/routes.js at main · nourcy/passbolt-poc-jwt · GitHub

And you can see how the encrypt and decrypt are required here: passbolt-poc-jwt/src/services/openpgp.service.js at main · nourcy/passbolt-poc-jwt · GitHub

Cheers :clinking_glasses:

1 Like

Hi @max ,

After reviewing your implementation, I found one difference that which turned out to be the solution: I was expressing the expiry in milliseconds. I put the 2 minutes expressed in seconds, like you, and now it works.

Thanks

1 Like

Thank you so much for this POC!
Unfortunately, I still can’t retrive the JWT token from the server.
I will continue to try to get it to work, otherwise I will try to use the old GPGAuth.

What error do you get?

After running npm start login this is what i get:

Response {
  status: 400,
  statusText: 'Bad Request',
  headers: Headers {
    'cache-control': 'no-store, no-cache, must-revalidate',
    'content-security-policy': "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self';frame-src 'self' https://*.duosecurity.com;",
    'content-type': 'application/json',
    date: 'Fri, 27 Sep 2024 09:38:47 GMT',
    expires: 'Thu, 19 Nov 1981 08:52:00 GMT',
    pragma: 'no-cache',
    server: 'nginx',
    'set-cookie': 'passbolt_session=0ab1u11e62cl2i6ukt5od5bur8; path=/; HttpOnly; SameSite=Lax, passbolt_session=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/, csrfToken=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/',
    'transfer-encoding': 'chunked'
  },
  body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },
  bodyUsed: false,
  ok: false,
  redirected: false,
  type: 'basic',
  url: 'https://passbolt.local/auth/jwt/login.json'
}

I had previously set the variables USER_ID, TRUSTED_DOMAIN and PASSPHRASE to the correct value.

I retrieved my USER_ID going to Users → Click on my name → Grabbing the UUID from the URL bar and I downloaded my private key from my profile.

I have not tested GPGAuth at the moment.

Some additional information that may help:

  • I’m running Passbolt on Docker behind Traefik
  • The certificate is self-signed and I temporarily disabled the headers.yaml file specified here
  • I’m currently running Passbolt 4.9.1
  • This is the result of ./bin/status-report
www-data@ef9fc76f3cf2:/usr/share/php/passbolt$ ./bin/status-report

     ____                  __          ____  
    / __ \____  _____ ____/ /_  ____  / / /_ 
   / /_/ / __ `/ ___/ ___/ __ \/ __ \/ / __/ 
  / ____/ /_/ (__  |__  ) /_/ / /_/ / / /    
 /_/    \__,_/____/____/_.___/\____/_/\__/   

 Open source password manager for teams
-------------------------------------------------------------------------------
Passbolt CE 4.9.1
Cakephp 4.5.2
Linux ef9fc76f3cf2 6.1.0-23-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.99-1 (2024-07-15) x86_64 GNU/Linux
PHP 8.2.20 (cli) (built: Jun 17 2024 13:33:14) (NTS)
 ERROR: /usr/share/php/passbolt/bin/utils.sh: line 64: mysql: command not found
gpg (GnuPG) 2.2.40
libgcrypt 1.10.1

     ____                  __          ____  
    / __ \____  _____ ____/ /_  ____  / / /_ 
   / /_/ / __ `/ ___/ ___/ __ \/ __ \/ / __/ 
  / ____/ /_/ (__  |__  ) /_/ / /_/ / / /    
 /_/    \__,_/____/____/_.___/\____/_/\__/   

 Open source password manager for teams
-------------------------------------------------------------------------------
 Healthcheck shell                                                           
-------------------------------------------------------------------------------

 Environment

 [PASS] PHP version 8.2.20.
 [PASS] PHP version is 8.1 or above.
 [PASS] PCRE compiled with unicode support.
 [PASS] Mbstring extension is installed.
 [PASS] Intl extension is installed.
 [PASS] GD or Imagick extension is installed.
 [PASS] The temporary directory and its content are writable and not executable.
 [PASS] The logs directory and its content are writable.

 Config files

 [PASS] The application config file is present
 [WARN] The passbolt config file is missing in /etc/passbolt/
 [HELP] Copy /etc/passbolt/passbolt.default.php to /etc/passbolt/passbolt.php
 [HELP] The passbolt config file is not required if passbolt is configured with environment variables

 Core config

 [PASS] Cache is working.
 [PASS] Debug mode is off.
 [PASS] Unique value set for security.salt
 [PASS] Full base url is set to https://passbolt.itteam.barsanti.edu.it
 [PASS] App.fullBaseUrl validation OK.
 [FAIL] Could not reach the /healthcheck/status with the url specified in App.fullBaseUrl
 [HELP] Check that the domain name is correct in /etc/passbolt/passbolt.php
 [HELP] Check the network settings

 SSL Certificate

 [WARN] SSL peer certificate does not validate.
 [WARN] Hostname does not match when validating certificates.
 [WARN] Using a self-signed certificate.
 [HELP] Check https://help.passbolt.com/faq/hosting/troubleshoot-ssl

 SMTP settings

 [PASS] The SMTP Settings plugin is enabled.
 [PASS] SMTP Settings coherent. You may send a test email to validate them.
 [PASS] The SMTP Settings source is: database.
 [WARN] The SMTP Settings plugin endpoints are enabled.
 [HELP] It is recommended to disable the plugin endpoints.
 [HELP] Set the PASSBOLT_SECURITY_SMTP_SETTINGS_ENDPOINTS_DISABLED environment variable to true.
 [HELP] Or set passbolt.security.smtpSettings.endpointsDisabled to true in /etc/passbolt/passbolt.php.
 [PASS] No custom SSL configuration for SMTP server.

 JWT Authentication

 [PASS] The JWT Authentication plugin is enabled.
 [PASS] The /etc/passbolt/jwt/ directory is not writable.
 [PASS] A valid JWT key pair was found.

 GPG Configuration

 [PASS] PHP GPG Module is installed and loaded.
 [PASS] The environment variable GNUPGHOME is set to /var/lib/passbolt/.gnupg.
 [PASS] The directory /var/lib/passbolt/.gnupg containing the keyring is writable by the webserver user.
 [PASS] The server OpenPGP key is not the default one.
 [PASS] The public key file is defined in /etc/passbolt/passbolt.php and readable.
 [PASS] The private key file is defined in /etc/passbolt/passbolt.php and readable.
 [PASS] The server key fingerprint matches the one defined in /etc/passbolt/passbolt.php.
 [PASS] The server public key defined in the /etc/passbolt/passbolt.php (or environment variables) is in the keyring.
 [PASS] There is a valid email id defined for the server key.
 [PASS] The public key can be used to encrypt a message.
 [PASS] The private key can be used to sign a message.
 [PASS] The public and private keys can be used to encrypt and sign a message.
 [PASS] The private key can be used to decrypt a message.
 [PASS] The private key can be used to decrypt and verify a message.
 [PASS] The public key can be used to verify a signature.
 [PASS] The server public key format is Gopengpg compatible.
 [PASS] The server private key format is Gopengpg compatible.

 Application configuration

 [PASS] Using latest passbolt version (4.9.1).
 [FAIL] Passbolt is not configured to force SSL use.
 [HELP] Set passbolt.ssl.force to true in /etc/passbolt/passbolt.php.
 [PASS] App.fullBaseUrl is set to HTTPS.
 [PASS] Selenium API endpoints are disabled.
 [PASS] Search engine robots are told not to index content.
 [INFO] The Self Registration plugin is enabled.
 [INFO] Registration is closed, only administrators can add users.
 [PASS] The deprecated self registration public setting was not found in /etc/passbolt/passbolt.php.
 [WARN] Host availability checking is disabled.
 [HELP] Make sure this instance is not publicly available on the internet.
 [HELP] Or set the PASSBOLT_EMAIL_VALIDATE_MX environment variable to true.
 [HELP] Or set passbolt.email.validate.mx to true in /etc/passbolt/passbolt.php.
 [PASS] Serving the compiled version of the javascript app.
 [WARN] Some email notifications are disabled by the administrator.
 [PASS] The database schema is up to date.

 Database

 [PASS] The application is able to connect to the database
 [PASS] 31 tables found.
 [PASS] Some default content is present.

 [FAIL] 2 error(s) found. Hang in there!


     ____                  __          ____  
    / __ \____  _____ ____/ /_  ____  / / /_ 
   / /_/ / __ `/ ___/ ___/ __ \/ __ \/ / __/ 
  / ____/ /_/ (__  |__  ) /_/ / /_/ / / /    
 /_/    \__,_/____/____/_.___/\____/_/\__/   

 Open source password manager for teams
-------------------------------------------------------------------------------
 Cleanup shell (dry-run)
-------------------------------------------------------------------------------
No issue found, data looks squeaky clean!

     ____                  __          ____  
    / __ \____  _____ ____/ /_  ____  / / /_ 
   / /_/ / __ `/ ___/ ___/ __ \/ __ \/ / __/ 
  / ____/ /_/ (__  |__  ) /_/ / /_/ / / /    
 /_/    \__,_/____/____/_.___/\____/_/\__/   

 Open source password manager for teams
-------------------------------------------------------------------------------
Data check shell
[PASS] Data integrity for AuthenticationTokens.
  [PASS] Can validate: 141/141
[PASS] Data integrity for Comments.
  [PASS] Can validate: 0/0
[PASS] Data integrity for Favorites.
  [PASS] Can validate: 0/0
[PASS] Data integrity for Gpgkeys.
  [PASS] Can encrypt: 6/6
  [PASS] Pass validation service checks: 6/6
  [PASS] Entity data and armored key data matches: 6/6
  [PASS] Is not expired: 6/6
  [PASS] Is armored key format valid: 6/6
[PASS] Data integrity for Groups.
  [PASS] Can validate: 1/1
[PASS] Data integrity for Profiles.
  [PASS] Can validate: 15/15
[PASS] Data integrity for Resources.
  [PASS] Can validate: 67/67
[PASS] Data integrity for Secrets.
  [PASS] Can validate: 301/301
[PASS] Data integrity for Users.
  [PASS] Can validate: 15/15

Thanks again for all the help!

I finally discovered why the authentication was failing.

The problem was related to how the verify_token_expiry field was represented. Instead of being a string like this:

{
  "version": "1.0.0",
  "domain": "https://passbolt.example.com",
  "verify_token": "af731273-47d3-42e9-a95b-afb64d801522",
  "verify_token_expiry": "1731780928"
}

It was serialized as a number, like this:

{
  "version": "1.0.0",
  "domain": "https://passbolt.itteam.barsanti.edu.it",
  "verify_token": "a8f3a156-3d84-41b8-a0a2-f26b2a2fe23b",
  "verify_token_expiry": 1731780928
}

Once I corrected this discrepancy, the authentication process worked as expected.