Understanding JWT (2024)

Atlassian Connect uses a technology called JWT (JSON Web Token)to authenticate apps. JSON Web Tokens (JWT) are a standard way of representing security claims betweenthe app and the Atlassian host product. A JWT token is a signed JSON object that containsinformation which enables the receiver to authenticate the sender of the request.

The Atlassian client frameworks take care of handling JWT tokens so you don't have to.See the README files for more information:

The format of a JWT token is: <base64url-encoded header>.<base64url-encoded claims>.<signature>.

  • The header specifies a very small amount of information that the receiver needs in order to parse and verify the JWT token.
  • The claims are a list of assertions that the issuer is making: each asserts that a particular field has a specific value.
  • The signature is computed by using an algorithm such as HMAC SHA-256 plus the header and claims sections.

For more information about the structure of a JWT token, see Manually creating a JWT.

Types of JWT token

Asymmetric JWT token

  • For install and uninstall lifecycle callback events, host product will issue an asymmetrically signed JWT token which can be used to validate that the request was made from Atlassian.

Symmetric JWT token (Using shared secret)

  • For other communications between the product and the app server, Atlassian issues two types of JWT to Connect apps.
  1. An iframe or server-to-server JWT, which includes a computed query string hash.

  2. A context JWT which is not tied to a particular URL. This will include a fixed qsh claim

    The Atlassian client frameworks also allow a JWT to be created and issued by the app, knownas a session token. These operate similarly to context JWTs as they too are not tied to a particular URL. They willalso have a fixed qsh value.

Creating a JWT token

The high-level steps in creating a JWT token are:

  1. Create a header JSON object.
  2. Convert the header JSON object to a UTF-8 encoded string and base64url encode it. That gives you encodedHeader.
  3. Create a claims JSON object, including a query string hash.
  4. Convert the claims JSON object to a UTF-8 encoded string and base64url encode it. That gives you encodedClaims.
  5. Concatenate the encoded header, a period character (.) and the encoded claims set. That gives you:signingInput = encodedHeader+ "." + encodedClaims.
  6. Compute the signature of signingInput using the JWT or cryptographic library of your choice. Then base64url encode it. That gives youencodedSignature.
  7. Concatenate the signing input, another period character and the signature, which gives you the JWT token:jwtToken = signingInput + "." + encodedSignature

You shouldn't have to create JWT tokens manually. There are libraries available in most languages,as we describe in the JWT libraries section. If you do need to create a token manually, readManually creating a JWT. In most cases, you should consider using theAtlassian client frameworks, which automatically handle JWT tokens for you.

Once you have the JWT token, you can use it to make calls by supplying it into the auth header like the following example:

12
POST https://<my-dev-environment>.atlassian.net/jira/rest/api/2/issue/AC-1/attachments"Authorization" header value: "JWT <jwt-token>"

JWT libraries

Most modern languages have JWT libraries available. We recommend you use one of these libraries(or other JWT-compatible libraries) before trying to hand-craft the JWT token.

The jwt.io site provideshandy web based forms for exploring Atlassian Connect JWT tokens. Bear in mind that JWTs are secure tokens andcare should be taken disclosing them to 3rd party sites.

Java example

Here is an example of creating a JWT token, in Java using atlassian-jwt and nimbus-jwt (last testedwith atlassian-jwt version 1.5.3 and nimbus-jwt version 2.16):

12
import com.atlassian.jwt.core.writer.JsonSmartJwtJsonBuilder;import com.atlassian.jwt.core.writer.JwtClaimsBuilder;import com.atlassian.jwt.core.writer.NimbusJwtWriterFactory;import com.atlassian.jwt.httpclient.CanonicalHttpUriRequest;import com.atlassian.jwt.writer.JwtJsonBuilder;import com.atlassian.jwt.writer.JwtWriterFactory;import java.io.UnsupportedEncodingException;import java.net.URI;import java.net.URISyntaxException;import java.net.http.HttpRequest;import java.security.NoSuchAlgorithmException;import java.util.HashMap;public class JWTSample { //the key from the app descriptor private String key = "atlassian-connect-addon"; //the sharedsecret key received during the app installation handshake private String sharedSecret = "this is not a secret that you should use in your code"; public HttpRequest createRequestWithJwt() throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException { String method = "GET"; String baseUrl = "https://<my-dev-environment>.atlassian.net/"; String contextPath = "/"; String apiPath = "/rest/api/latest/serverInfo"; String jwt = createJwt(method, apiPath, contextPath); HttpRequest request = HttpRequest.newBuilder() .uri(new URI(baseUrl + apiPath)) .header("Content-Type", "application/json") .header("Authorization", "JWT " + jwt) .build(); return request; } private String createJwt(String method, String apiPath, String contextPath) throws UnsupportedEncodingException, NoSuchAlgorithmException { long issuedAt = System.currentTimeMillis() / 1000L; long expiresAt = issuedAt + 180L; JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder() .issuedAt(issuedAt) .expirationTime(expiresAt) .issuer(key); CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method, apiPath, contextPath, new HashMap()); JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical); JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory(); String jwtbuilt = jwtBuilder.build(); String jwtToken = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256, sharedSecret).jsonToJwt(jwtbuilt); return jwtToken; }}

Decoding and verifying a JWT token

The high-level steps of decoding and verifying a JWT token are:

  1. Extract the JWT token from the request's authorization header (using a jwt query parameter is deprecated)
  2. Decode the JWT token without verification. This gives you a header JSON object, a claims JSON object,and a signature.
  3. Extract the issuer (iss) claim from the decoded, unverified claims object. This is the clientKeyfor the tenant -an identifier for the Atlassian product making the call, which should have been stored by the app as part of theinstallation handshake.
  4. Look up the sharedSecret for the clientKey, as stored by the app during the installation handshake
  5. Verify the signature with the algorithm specified in the header's alg field.sharedSecret is used to verify HS256 algorithm, and for signed lifecycle callback eventswith RS256 algorithm verify using a public key.
  6. For JWTs with a computed qsh value, verify the query has not been tampered byCreating a query hashand comparing it against the qsh claim on the verified token.
  7. For context JWTs and session tokens, the qsh claim will be present but will be a fixed value 'context-qsh'. Standard qshvalidation will fail on such values. You must make a deliberate decision to accept such context JWTs and session tokenson a given endpoint. Do not accept context JWTs in module or lifecycle endpoints. You should only accept one type ofJWT per endpoint.
  8. Verify standard claimsif present. Issuers include these to help you ensure that tokens you receive are used according to the intentions ofthe issuer and with the best possible results.

These steps must be executed before processing the request, and the request must be rejected if any ofthese steps fail.

Java example

Here is a minimal example of decoding and verifying a JWT token, in Java, using atlassian-jwt and nimbus-jwt (last tested with atlassian-jwt version 1.5.3 and nimbus-jwt version 2.16).

NOTE: This example does not include any error handling.See AbstractJwtAuthenticatorfrom atlassian-jwt for recommendations of how to handle the different error cases.

1234567891011121314151617181920212223242526272829
import com.atlassian.jwt.*;import com.atlassian.jwt.core.http.JavaxJwtRequestExtractor;import com.atlassian.jwt.core.reader.*;import com.atlassian.jwt.exception.*;import com.atlassian.jwt.reader.*;import javax.servlet.http.HttpServletRequest;import java.io.UnsupportedEncodingException;import java.security.NoSuchAlgorithmException;import java.util.Map;public class JWTVerificationSample { public Jwt verifyRequest(HttpServletRequest request, JwtIssuerValidator issuerValidator, JwtIssuerSharedSecretService issuerSharedSecretService) throws UnsupportedEncodingException, NoSuchAlgorithmException, JwtVerificationException, JwtIssuerLacksSharedSecretException, JwtUnknownIssuerException, JwtParseException { JwtReaderFactory jwtReaderFactory = new NimbusJwtReaderFactory( issuerValidator, issuerSharedSecretService); JavaxJwtRequestExtractor jwtRequestExtractor = new JavaxJwtRequestExtractor(); CanonicalHttpRequest canonicalHttpRequest = jwtRequestExtractor.getCanonicalHttpRequest(request); Map<String, ? extends JwtClaimVerifier> requiredClaims = JwtClaimVerifiersBuilder.build(canonicalHttpRequest); String jwt = jwtRequestExtractor.extractJwt(request); return jwtReaderFactory.getReader(jwt).readAndVerify(jwt, requiredClaims); }}

Decoding a JWT token

Decoding the JWT token reverses the steps followed during the creation of the token,to extract the header, claims and signature. Here is an example in Java:

12
String jwtToken = ...;//e.g. extracted from the requestString[] base64UrlEncodedSegments = jwtToken.split('.');String base64UrlEncodedHeader = base64UrlEncodedSegments[0];String base64UrlEncodedClaims = base64UrlEncodedSegments[1];String signature = base64UrlEncodedSegments[2];String header = base64Urldecode(base64UrlEncodedHeader);String claims = base64Urldecode(base64UrlEncodedClaims);

This gives us the following:

Header:

12
{ "alg": "HS256", "typ": "JWT"}

Claims:

12
{ "iss": "jira:15489595", "iat": 1386898951, "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9", "exp": 1386899131}

Signature:

12
uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo

JWT libraries typically provide methods to be able to verify a received JWT token.Here is an example using nimbus-jose-jwt and json-smart:

12
import com.nimbusds.jose.JOSEException;import com.nimbusds.jose.JWSObject;import com.nimbusds.jose.JWSVerifier;import com.nimbusds.jwt.JWTClaimsSet;import net.minidev.json.JSONObject;public JWTClaimsSet read(String jwt, JWSVerifier verifier) throws ParseException, JOSEException{ JWSObject jwsObject = JWSObject.parse(jwt); if (!jwsObject.verify(verifier)) { throw new IllegalArgumentException("Fraudulent JWT token: " + jwt); } JSONObject jsonPayload = jwsObject.getPayload().toJSONObject(); return JWTClaimsSet.parse(jsonPayload);}

Verifying a asymmetric JWT token for install callbacks

JWT token for lifecycle callback events are signed with the RS256 algorithm and requires a public key for validation. This public key can be retrieved from our CDN(https://connect-install-keys.atlassian.com/).

  1. Get kid from JWT token header

Decoded header will have kid parameter for asymmetric JWT token: see Decoding a JWT token

12
{ "kid": "0e50fccb-239d-4991-a5db-dc850ba3f236", "typ": "JWT", "alg": "RS256"}
  1. Fetch public key from CDN

Public key format: PEM-Encoded PKCS#8 Format in plain-text

12
https://connect-install-keys.atlassian.com/0e50fccb-239d-4991-a5db-dc850ba3f236
  1. Validate JWT with RS256 signature (RSA signature with SHA-256)
12
import com.nimbusds.jose.*;import com.nimbusds.jose.crypto.*;import com.nimbusds.jwt.*;import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;import org.bouncycastle.openssl.PEMParser;import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;import java.io.Reader;import java.io.StringReader;import java.security.interfaces.RSAPublicKey;public boolean verify(String jwt, String privateKey) { try (Reader reader = new StringReader(privateKey)) { PEMParser pemParser = new PEMParser(reader); Object object = pemParser.readObject(); SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(object); JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); RSAPublicKey rsaPublicKey = (RSAPublicKey) converter.getPublicKey(pub); SignedJWT signedJWT = SignedJWT.parse(jwt); JWSVerifier verifier = new RSASSAVerifier(rsaPublicKey); return signedJWT.verify(verifier); } catch (Exception e) { // log and handle exception return false; }}
  1. Checking the standard claim for install callback

Verify standard claims if present.

The JWT token for lifecycle callback events includes the aud(Audience) claim which matches the app's baseUrl.

Here is an example to check if the audience claim matches the app base url manually:

12
import com.nimbusds.jose.*;import com.nimbusds.jwt.*;import java.net.URI;private static final String APP_BASE_URL = "https://example.com/path/";public boolean verifyAudienceClaim(String jwt) { // `aud` claim should match your app baseUrl defined in the descriptor file. final JWSObject jwsObject = JWSObject.parse(jwt); JWTClaimsSet claims = JWTClaimsSet.parse(jwsObject.getPayload().toString()); String audienceClaim = claims.getAudience().stream().findFirst().orElse(""); URI appBaseUrl = URI.create(APP_BASE_URL); return audienceClaim.equals(appBaseUrl.toString());}

Creating a query string hash

A query string hash is a signed canonical request for the URI of the API you want to call.

12
qsh = `sign(canonical-request)`canonical-request = `canonical-method + '&' + canonical-URI + '&' + canonical-query-string`

A canonical request is a normalised representation of the URI. Here is an example. For the following URL,assuming you want to do a "GET" operation:

12
"https://<my-dev-environment>.atlassian.net/path/to/service?zee_last=param&repeated=parameter 1&first=param&repeated=parameter 2"

The canonical request is

12
"GET&/path/to/service&first=param&repeated=parameter%201,parameter%202&zee_last=param"

To create a query string hash, follow the detailed instructions below:

  1. Compute canonical method
    • Simply the upper-case of the method name (e.g. "GET" or "PUT")
  2. Append the character '&'
  3. Compute canonical URI
    • Discard the protocol, server, port, context path and query parameters from the full URL.
    • For requests targeting apps discard the baseUrl in the app descriptor.
    • Removing the context path allows a reverse proxy to redirect incoming requests for "jira.example.com/getsomething"to "example.com/jira/getsomething" without breaking authentication. The requester cannot know that the reverse proxywill prepend the context path "/jira" to the originally requested path "/getsomething"
    • Empty-string is not permitted; use "/" instead.
    • Url-encode any '&' characters in the path.
    • Do not suffix with a '/' character unless it is the only character. e.g.
      • Canonical URI of "https://example.atlassian.net/wiki/some/path/?param=value" is "/some/path"
      • Canonical URI of "https://example.atlassian.net" is "/"
  4. Append the character '&'
  5. Compute canonical query string
    • The query string will use percent-encoding.
    • Sort the parameters primarily by their percent-encoded names and secondarily by their percent-encoded values.
      • Include all the parameters from query string if it is a GET request.
      • Include all the post data in payload if it is a POST request.
    • Sorting is by codepoint: sort(["a", "A", "b", "B"]) => ["A", "B", "a", "b"]
    • For each parameter append its percent-encoded name, the '=' character and then its percent-encoded value. If the parameter has no value, the '=' character must still be included: &a=&b=foo&c=
    • In the case of repeated parameters, append the encoded ',' character (i.e., "%2C") and subsequent percent-encoded values.
    • Ignore the jwt parameter, if present.
    • Some particular values to be aware of:
      • A whitespace character is encoded as "%20",
      • "," as "%2C",
      • "+" as "%2B",
      • "*" as "%2A" and
      • "~" as "~".
        (These values used for consistency with OAuth1.)
  6. Convert the canonical request string to bytes
    • The encoding used to represent characters as bytes is UTF-8
  7. Hash the canonical request bytes using the SHA-256 algorithm
    • e.g. The SHA-256 hash of "foo" is "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"

As discussed above, context JWTs and session tokens will have a qsh claim, but it will contain a fixed value and willnot validate against the above procedure.

Manually creating a JWT

You should only need to read this section if you are planning to create JWT tokens manually, i.e. if you are not using one of the libraries listed in the previous section

A JWT token looks like this:

12
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo

The basic format is:

12
<base64url-encoded header>.<base64url-encoded claims>.<base64url-encoded signature>

In other words:

  • You create a header object, with the JSON format. Then you encode it in base64url
  • You create a claims object, with the JSON format. Then you encode it in base64url
  • You create a signature for the URI (we'll get into that later). Then you encode it in base64url
  • You concatenate the three items, with the "." separator

You shouldn't actually have to do this manually, as there are libraries available in most languages, as we describe in the JWT libraries section.

However it is important you understand the fields in the JSON header and claims objects described in the next sections:

Header

The header object declares the type of the encoded object and the algorithm used for the cryptographic signature. Atlassian Connect alwaysuses the same values for these. The typ property will be "JWT" and the alg property will be "HS256".

12
{ "typ":"JWT", "alg":"HS256"}
AttributeTypeDescription
typStringType for the token, defaulted to "JWT". Specifies that this is a JWT token
alg (mandatory)StringAlgorithm. specifies the algorithm used to sign the token. In atlassian-connect version 1.0 we support the HMAC SHA-256 algorithm, which the JWT specification identifies using the string HS256.

Your JWT library or implementation should discard any tokens which specify `alg: none` as this can provide a bypass of the token verification.

Claims

The claims object contains security information about the message you're transmitting. The attributes of this object provide information to ensure the authenticity of the claim. The information includes the issuer, when the token was issued, when the token will expire, and othercontextual information, described below.

12
{ "iss": "jira:1234567", "iat": 1300819370, "exp": 1300819380, "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9", "sub": "123456:1234abcd-1234-abcd-1234-1234abcd1234"}
AttributeTypeDescription
iss (mandatory)Stringthe issuer of the claim. Connect uses it to identify the application making the call. for example:
  • If the Atlassian product is the calling application: contains the unique identifier of the tenant. This is the `clientKey` that you receive in the `installed` callback. You should reject unrecognised issuers.
  • If the app is the calling application: the app key specified in the app descriptor
iat (mandatory)LongIssued-at time. Contains the UTC Unix time at which this token was issued. There are no hard requirements around this claim but it does not make sense for it to be significantly in the future. Also, significantly old issued-at times may indicate the replay of suspiciously old tokens.
exp (mandatory)LongExpiration time. It contains the UTC Unix time after which you should no longer accept this token. It should be after the issued-at time.
qsh (mandatory)Stringquery string hash. A custom Atlassian claim that prevents URL tampering in appropriate JWTs.
sub (optional)StringThe subject of this token. This is the user associated with the action, defined by their Atlassian Account ID. If there is no user logged in, this attribute may not be present.
aud (optional)String or String[]The audience(s) of this token. For REST API calls from an app to a product, the audience claim can be used to disambiguate the intended recipients. For Jira and Confluence, it is only being set for lifecycle callbacks at the moment, but will become mandatory when making REST calls from an app to e.g. the bitbucket.org domain.
context (optional)ObjectThe context claim is an extension added by Atlassian Connect that may contain useful context for outbound requests (from the product to your app).

You should use a little leeway when processing time-based claims, as clocks may drift apart.The JWT specification suggests no more than a few minutes.Judicious use of the time-based claims allows for replays within a limited window.This can be useful when all or part of a page is refreshed or when it is valid for a user torepeatedly perform identical actions (e.g. clicking the same button).

Signature

The signature of the token is a combination of a hashing algorithm combined with the header and claims sections of the token.This provides a way to verify that the claims and headers haven't been been compromised during transmission. The signaturewill also detect if a different secret is used for signing. In the JWT spec, there are multiple algorithms you can use to create thesignature, but Atlassian Connect uses the HMAC SHA-256 algorithm. If the JWT token has no specified algorithm, you should discard thattoken as they're not able to be signature verified.

Example

Here is an example in Java using gson, commons-codec, and the Java security and crypto libraries:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
public class JwtClaims { protected String iss; protected long iat; protected long exp; protected String qsh; protected String sub; // + getters/setters/constructors}[...]public class JwtHeader { protected String alg; protected String typ; // + getters/setters/constructors}[...]import static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;import static org.apache.commons.codec.binary.Hex.encodeHexString;import java.io.UnsupportedEncodingException;import java.security.*;import javax.crypto.*;import javax.crypto.spec.SecretKeySpec;import com.google.gson.Gson;public class JwtBuilder { public static String generateJWTToken(String requestUrl, String canonicalUrl, String key, String sharedSecret) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException { JwtClaims claims = new JwtClaims(); claims.setIss(key); claims.setIat(System.currentTimeMillis() / 1000L); claims.setExp(claims.getIat() + 180L); claims.setQsh(getQueryStringHash(canonicalUrl)); String jwtToken = sign(claims, sharedSecret); return jwtToken; } private static String sign(JwtClaims claims, String sharedSecret) throws InvalidKeyException, NoSuchAlgorithmException { String signingInput = getSigningInput(claims, sharedSecret); String signed256 = signHmac256(signingInput, sharedSecret); return signingInput + "." + signed256; } private static String getSigningInput(JwtClaims claims, String sharedSecret) throws InvalidKeyException, NoSuchAlgorithmException { JwtHeader header = new JwtHeader(); header.alg = "HS256"; header.typ = "JWT"; Gson gson = new Gson(); String headerJsonString = gson.toJson(header); String claimsJsonString = gson.toJson(claims); String signingInput = encodeBase64URLSafeString(headerJsonString .getBytes()) + "." + encodeBase64URLSafeString(claimsJsonString.getBytes()); return signingInput; } private static String signHmac256(String signingInput, String sharedSecret) throws NoSuchAlgorithmException, InvalidKeyException { SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(key); return encodeBase64URLSafeString(mac.doFinal(signingInput.getBytes())); } private static String getQueryStringHash(String canonicalUrl) throws NoSuchAlgorithmException,UnsupportedEncodingException { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(canonicalUrl.getBytes("UTF-8")); byte[] digest = md.digest(); return encodeHexString(digest); } }[...]public class Sample { public HttpRequest getUrlSample() throws Exception { String requestUrl = "https://<my-dev-environment>.atlassian.net/rest/atlassian-connect/latest/license"; String canonicalUrl = "GET&/rest/atlassian-connect/latest/license&"; String key = "..."; //from the app descriptor //and received during installation handshake String sharedSecret = "..."; //received during installation Handshake String jwtToken = JwtBuilder.generateJWTToken( requestUrl, canonicalUrl, key, sharedSecret); HttpRequest request = HttpRequest.newBuilder() .uri(new URI(requestUrl)) .header("Content-Type", "application/json") .header("Authorization", "JWT " + jwtToken) .build(); return request; }}

As an expert in Atlassian Connect and JSON Web Tokens (JWT), I can attest to the comprehensive and accurate information provided in the article. The author has demonstrated a deep understanding of the technology, and the details shared are in line with best practices for implementing Atlassian Connect and handling JWT tokens securely.

Let's break down the key concepts covered in the article:

  1. Atlassian Connect and JWT Authentication:

    • Atlassian Connect uses JSON Web Tokens (JWT) for authenticating apps.
    • JWTs are standard for representing security claims between the app and the Atlassian host product.
    • JWT tokens are signed JSON objects containing information to authenticate the sender of the request.
  2. JWT Token Structure:

    • JWT tokens consist of three parts: base64url-encoded header, base64url-encoded claims, and a signature.
    • The header specifies minimal information needed for parsing and verifying the token.
    • Claims are assertions made by the issuer about specific fields.
    • The signature is computed using an algorithm like HMAC SHA-256.
  3. Types of JWT Tokens:

    • Asymmetric JWT tokens are issued for install and uninstall lifecycle callback events.
    • Symmetric JWT tokens are used for communication between the product and the app server, including iframe or server-to-server JWT and context JWT.
  4. Creating a JWT Token:

    • High-level steps involve creating header and claims JSON objects, encoding them, and computing the signature.
    • Various libraries are recommended for different programming languages to simplify token creation.
  5. JWT Libraries:

    • Recommended libraries for different languages include atlassian-jwt (Java), pyjwt (Python), atlassian-jwt-js (Node.js), atlassian-jwt-ruby (Ruby), firebase php-jwt, and luciferous jwt (PHP), jwt (C#), haskell-jwt (Haskell).
  6. Decoding and Verifying a JWT Token:

    • Steps involve extracting, decoding, and verifying the JWT token.
    • The issuer, shared secret, and signature are crucial for verification.
  7. Java Examples:

    • Detailed Java examples are provided for creating, decoding, and verifying JWT tokens using atlassian-jwt and nimbus-jwt libraries.
  8. Query String Hash and Token Verification:

    • A query string hash is used for some JWTs to prevent URL tampering.
    • Steps for creating a query string hash are detailed, including the canonical request and hash computation.
  9. Manual JWT Creation:

    • While manual JWT creation is discouraged, the article provides details on the structure of JWT, including the header, claims, and signature.
  10. Example Code:

    • The article includes Java examples for creating, decoding, and verifying JWT tokens, demonstrating practical implementation.

In summary, the article is a valuable resource for developers working with Atlassian Connect and JWT authentication, providing a clear and detailed guide on token structure, creation, and verification. The inclusion of code examples enhances its practical utility for implementation.

Understanding JWT (2024)
Top Articles
Latest Posts
Article information

Author: Greg Kuvalis

Last Updated:

Views: 6310

Rating: 4.4 / 5 (75 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Greg Kuvalis

Birthday: 1996-12-20

Address: 53157 Trantow Inlet, Townemouth, FL 92564-0267

Phone: +68218650356656

Job: IT Representative

Hobby: Knitting, Amateur radio, Skiing, Running, Mountain biking, Slacklining, Electronics

Introduction: My name is Greg Kuvalis, I am a witty, spotless, beautiful, charming, delightful, thankful, beautiful person who loves writing and wants to share my knowledge and understanding with you.