Using JWT with Google App Engine

Issue #33 closed
arpit1712 created an issue

GAE allows to sign any random bytes using application's unique private key (https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/appidentity/AppIdentityService). It however, doesn't provide access to private key. It also doesn't mention which algorithm is used to sign. But it provides all public certificates for verification. Is it possible to add support for such use case?

A related question on stackoverflow: http://stackoverflow.com/questions/31773831/using-jwt-with-google-app-engine

Comments (4)

  1. Brian Campbell repo owner

    I don't know if it's possible. It depends on how the GAE AppIdentityService.signForApp(byte[] signBlob) actually works. And I wasn't able to find much info with a little searching. I put this out on twitter https://twitter.com/__b_c/status/628216588429168640 but haven't gotten any reply.

    If they sign using SHA256withRSA, which is reasonably likely, then it could probably be made to work with a little hacking. I modified the example from https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples to show what it might look like to sign and verify with AppIdentityService. I don't use GAE so could you please try this and let me know if/how it works?

    The alternative would be to manage/generate(see RsaJwkGenerator or EcJwkGenerator)/store/publish keys yourself and use the normal jose4j interfaces. But I do understand that using the infrastructure provided by GAE is appealing. Though I suppose it's worth mentioning that, even if you can get this to work, things could break down the road, if Google ever were to change how they do the signing.

        // Create the Claims, which will be the content of the JWT
        JwtClaims claims = new JwtClaims();
        claims.setIssuer("Issuer");  // who creates the token and signs it
        claims.setAudience("Audience"); // to whom the token is intended to be sent
        claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now)
        claims.setGeneratedJwtId(); // a unique identifier for the token
        claims.setIssuedAtToNow();  // when the token was issued/created (now)
        claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
        claims.setSubject("subject"); // the subject/principal is whom the token is about
        claims.setClaim("email","mail@example.com"); // additional claims/attributes about the subject can be added
        List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
        claims.setStringListClaim("groups", groups); // multi-valued claims work too and will end up as a JSON array
    
        // A JWT is a JWS and/or a JWE with JSON claims as the payload.
        // In this example it is a JWS so we create a JsonWebSignature object.
        JsonWebSignature jws = new JsonWebSignature();
    
        // The payload of the JWS is JSON content of the JWT Claims
        jws.setPayload(claims.toJson());
    
        // Set the signature algorithm on the JWT - only to single to the receiver as we will do the signing w/ GAE below
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
    
        // Sign and build the JWT/JWS using the GAE AppIdentityService
        String signingInput = CompactSerializer.serialize(jws.getHeaders().getEncodedHeader(), jws.getEncodedPayload());
        AppIdentityService.SigningResult signingResult = fakeAppIdentityService.signForApp(StringUtil.getBytesAscii(signingInput));
        String encodedSignature = Base64Url.encode(signingResult.getSignature());
        String jwt = CompactSerializer.serialize(signingInput, encodedSignature);
    
    
        // Now you can do something with the JWT. Like send it to some other party
        // over the clouds and through the interwebs.
        System.out.println("JWT: " + jwt);
    
        // build an X509VerificationKeyResolver with the public certificates from AppIdentityService
        Collection<PublicCertificate> publicCertificatesForApp = fakeAppIdentityService.getPublicCertificatesForApp();
        X509Util x509Util = new X509Util();
        List<X509Certificate> certificates = new ArrayList<>(publicCertificatesForApp.size());
        for (PublicCertificate pc : publicCertificatesForApp)
        {
            String x509CertificateInPemFormat = pc.getX509CertificateInPemFormat();
            X509Certificate certificate = x509Util.fromBase64Der(x509CertificateInPemFormat);
            certificates.add(certificate);
        }
    
        X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(certificates);
        x509VerificationKeyResolver.setTryAllOnNoThumbHeader(true); // try all the the public certificates from AppIdentityService
    
    
        // Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
        // be used to validate and process the JWT.
        // The specific validation requirements for a JWT are context dependent, however,
        // it typically advisable to require a expiration time, a trusted issuer, and
        // and audience that identifies your system as the intended recipient.
        // If the JWT is encrypted too, you need only provide a decryption key or
        // decryption key resolver to the builder.
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by
                .setExpectedAudience("Audience") // to whom the JWT is intended for
                .setVerificationKeyResolver(x509VerificationKeyResolver) // use the X509VerificationKeyResolver to find the verification key
                .build(); // create the JwtConsumer instance
    
        try
        {
            //  Validate the JWT and process it to the Claims
            JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
            System.out.println("JWT validation succeeded! " + jwtClaims);
        }
        catch (InvalidJwtException e)
        {
            // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
            // Hopefully with meaningful explanations(s) about what went wrong.
            System.out.println("Invalid JWT! " + e);
        }
    
  2. arpit1712 reporter

    Apologies for delayed response. I chose a different technique to sign token (Using user's password's hash). Also, for simplicity, I decided to use HMAC instead of RSA for signing the JWT.

    ~Arpit

  3. Log in to comment