Wiki: Spring Boot (Java) - How to sign a JWT to be used in wiki.js for SSO

Created on 27 Jun 2019  路  5Comments  路  Source: Requarks/wiki

Question
Since wiki.js uses JWT, I would like to like to utilize the JWT token in my current application to accomplish a single sign-on (SSO) experience. If a user is logged into the application, then the user should not have to log in again when accessing the wiki. I have a Spring Boot (Java) backend and I am having trouble signing the JWT, wikijs gives me forbidden errors when accessing the admin page. I have been referencing server\models\users.j:refreshToken to see how the token is signed. I was thrown off because it looks like the token is signed with both a private cert and a passphrase.

Can you please give me guidance on how to sign my token?

Host Info
OS: [Windows]
Wiki.js version: [e.g. 2.0.0-beta.180]
Spring Version: 2.1.4.RELEASE (spring-boot-starter-parent)
Java Version: 8

documentation

Most helpful comment

@bwalsh They are not the same no. The key in your config.yml is for HTTPS purposes only. The key used to sign the JWT is stored in the database under the settings table.

All 5 comments

With the help of @nicolasg-playster I was able to resolve this issue. I have attached an example jwt junit test as an example of how to implement this. I have used this implementation to successfully embed an iframe in my current application while having the current user already logged into the wiki when they navigate to the page containing the iframe.

Here is a snippet of how to get the decrypted private key from the pem cert created in setup.js. You can use that key to be able to sign a jwt that wikijs will be able to parse. Finally, you will obviously need to populate the claims in the jwt to match server\models\users.js. This token is stored as a cookie with the name jwt.

// this is where the magic happens...
// https://stackoverflow.com/questions/1774469/how-does-the-rsa-private-key-passphrase-work-under-the-hood
// https://github.com/bcgit/bc-java/blob/c0d5ac5e62a6531f640bdfb4897a9fe95a0c9fdb/pkix/src/test/java/org/bouncycastle/openssl/test/ParserTest.java#L412
public PrivateKey loadPrivateKey() throws IOException {
    // pem key converter - used to convert an encrypted PEMKeyPair to a decrypted KeyPair
    // BC = Bouncy Castle
    JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");

    // pem decryptor - needed to decrypt the PEMKeyPair from the pem file and needs to be built with the passcode used
    //                 to create the private key
    PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(passcode.toCharArray());

    // PEMParser - Class for parsing OpenSSL PEM encoded streams containingX509 certificates, PKCS8 encoded keys and PKCS7 objects. 
    PEMParser pr = openPEMResource(fileName);
    Object o = pr.readObject();

    if (!(o instanceof PEMKeyPair) && !(o instanceof PEMEncryptedKeyPair)) {
        throw new RuntimeException("error reading private key from disk");
    }
    // https://stackoverflow.com/questions/3711754/why-java-security-nosuchproviderexception-no-such-provider-bc
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

    // this is most likely a PEMEncryptedKeyPair
    // 1. decrypt the pem file using the decrypting provider built with the private key passcode
    //    this will produce a PEMKeyPair which is a holder for a public and private key (these values are not longer encrypted)
    // 2. convert org.bouncycastle.openssl.PEMKeyPair to a java.security.KeyPair
    KeyPair kp = (o instanceof PEMEncryptedKeyPair) ?
        converter.getKeyPair(((PEMEncryptedKeyPair)o).decryptKeyPair(decProv)) : converter.getKeyPair((PEMKeyPair)o);

    // we now have a decrypted public and private key ~ woohoo!
    return kp.getPrivate();        
}

// helper for getting a stream of the pem file
private PEMParser openPEMResource(String fileName) throws FileNotFoundException{
    // load file from disk
    InputStreamReader inputStream = new InputStreamReader(new FileInputStream(fileName));

    // read file
    Reader fRd = new BufferedReader(inputStream);

    // return a objected used to parse pem files
    return new PEMParser(fRd);
}

For the full example please see:
wikijs-jwt-example.txt

openPEMResource can be modified to use a StringReader if you are reading the private cert from the wiki db.

private PEMParser openPEMResource(String privateCert){
    Reader fRd = new StringReader(privateCert);
    return new PEMParser(fRd);
}

@fourgates @NGPixel : newbie question - Is the pem file mentioned here the same one used to configure SSL in config.yml ?

ssl:
  enabled: true
  port: 3443
  provider: custom

  format: pem
  key: path/to/key.pem
  cert: path/to/cert.pem
  passphrase: null
  dhparam: null

i think in my case i used the same private key. i used the pfx format though (another ssl option)

@bwalsh They are not the same no. The key in your config.yml is for HTTPS purposes only. The key used to sign the JWT is stored in the database under the settings table.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sbonaime picture sbonaime  路  3Comments

raikoug picture raikoug  路  3Comments

leandro-hermes picture leandro-hermes  路  4Comments

den1622 picture den1622  路  3Comments

gruesomehit picture gruesomehit  路  4Comments