Jira Service Desk problem: custom JWT verification for external API calls in atlassian-jwt plugin 3.1.0

We recently have a problem with our app. When a customer installs Jira Service Desk, our external API calls are not properly authorized, which results in a 401. After a bit of investigation, the cause of it seems to be the Atlassian JWT plugin (which is installed along with JSD). Long story short, the JWT token our calls use have an issuer and shared secret unrecognised by the plugin, which causes them to be rejected.

I have spent a considerable amount of time trying to solve it, let me describe what I tried so far.

According to Bitbucket, the newest version (3.1.0) of the JWT plugin allows you to register your own claim verifiers along with your issuer and shared secret, through the newest SPI. I made sure 3.1.0 is installed by my plugin (the last JSD uses 3.0.0 by default, where the SPI would not be available yet). That went without problems

Next, I implemented a simplified Jwt Issuer registry and Jwt Issuer Claim Verifier registry classes, so that Jira could pick up the right data for my plugin. Similarly, I implemented an example Claim verifier builder, which would be returned by my registry. (the code is in Scala)

@ExportAsService({
  Array(classOf[JwtIssuerRegistry])
})
@Component
@Named("pluginJwtIssuerRegistry")
class PluginJwtIssuerRegistry extends JwtIssuerRegistry {
  case class PluginJwtIssuer(name: String, sharedSecret: String) extends JwtIssuer {
    override def getName: String = name

    override def getSharedSecret: String = sharedSecret
  }

  override def getIssuer(issuer: String): JwtIssuer = PluginJwtIssuer(issuer, "sharedSecretPlaceholder")
}

@ExportAsService({
  Array(classOf[JwtIssuerClaimVerifiersRegistry])
})
@Component
@Named("pluginJwtIssuerClaimVerifiersRegistry")
class PluginJwtIssuerClaimVerifiersRegistry extends JwtIssuerClaimVerifiersRegistry {
  override def getClaimVerifiersBuilder(issuer: String): JwtClaimVerifiersBuilder =
    new ServerCustomJwtClaimVerifierBuilder(false)
}

class ServerCustomJwtClaimVerifierBuilder(verifyHash: Boolean) extends JwtClaimVerifiersBuilder {
  override def build(request: CanonicalHttpRequest): util.Map[String, _ <: JwtClaimVerifier] =
    ClaimVerifiers.NO_REQUIRED_CLAIMS
}

In order to ensure those classes are visible to the JWT plugin, I added the following configuration in the section of the AMPS in my plugin pom.xml:

<Export-Package>
       com.servicerocket.jira.salesforce.rest.auth.jwt,
       com.atlassian.jwt
 </Export-Package>

The registries definitely do show up in the OSGI view of the UPM in jira

This seems to be exactly in sync with the atlassian-spring-scanner-maven-test-service-exporting-plugin example provided in the atlassian spring scanner ( Bitbucket )

However, none of it seems to be picked up by the JWT plugin. When I connect my debugger to the com.atlassian.jwt.internal.PluginJwtRegistry.java class, the only Issuer registries visible to the tracker seem to be coming from Applinks and JSD; no sign of the services I exported.

Since the stack for our app is Scala, I was a bit worried about the compatibility of the Java annotations. And so, I also tried declaring the beans explicitly in the plugin-context.xml in META-INF:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:osgi="http://www.springframework.org/schema/osgi"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:atlassian-scanner="http://www.atlassian.com/schema/atlassian-scanner"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/osgi
        http://www.springframework.org/schema/osgi/spring-osgi.xsd
        http://www.atlassian.com/schema/atlassian-scanner
        http://www.atlassian.com/schema/atlassian-scanner/atlassian-scanner.xsd"
       default-autowire="autodetect">
    <atlassian-scanner:scan-indexes/>
    <beans:bean id="pluginJwtIssuerClaimVerifiersRegistry"
                class="com.servicerocket.jira.salesforce.rest.auth.jwt.PluginJwtIssuerClaimVerifiersRegistry"/>
    <osgi:service id="pluginJwtIssuerClaimVerifiersRegistry_osgiService" ref="pluginJwtIssuerClaimVerifiersRegistry"
                  interface="com.atlassian.jwt.JwtIssuerClaimVerifiersRegistry"/>
    <beans:bean id="pluginJwtIssuerRegistry"
                class="com.servicerocket.jira.salesforce.rest.auth.jwt.PluginJwtIssuerRegistry"/>
    <osgi:service id="pluginJwtIssuerRegistry_osgiService" ref="pluginJwtIssuerRegistry"
                  interface="com.atlassian.jwt.JwtIssuerRegistry"/>
</beans>

That didn’t achieve anything, either. The registry beans declared this way show up in the OSGI UPM view just the same, and they’re also not picked up by the Atlassian JWT plugin.

TL;DR how to make this work? There is no actual examples anywhere on how to use this SPI, and this is a blocker for a number of our customers.

5 Likes

I don’t think bundling your own version of atlassian-jwt is going to work. You need JSD to be using version 3.1.0. Other than that, the way you’re exporting your services to OSGi looks correct (as you’ve seen in the “Registered services” part of UPM’s OSGi view).

3 Likes

Thanks @aswan. We fixed the issue by make the jwt-plugin as one of our plugin dependencies. We appreciated your help.

@piotr.chabelski thank you for sharing this question, we are wrestling with this issue for a lot of time.

The problem I have is that when I add provided dependency it does pick up the registry properly, however, when there is no Service desk installation, plugin can’t load because obviously there is no jwt-plugin available. With compile scope, plugin loads but registry doesn’t work :frowning: (Jira 7.x)

<dependency>
            <groupId>com.atlassian.jwt</groupId>
            <artifactId>jwt-plugin</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
</dependency>