AWS Cognito JWT token validation - spring

I am trying to enforce security based on AWS Cognito JWT token, the source is at https://github.com/IxorTalk/ixortalk.aws.cognito.jwt.security.filter.
But I have concern, which is per documentation http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-identity-user-pools-using-id-and-access-tokens-in-web-api
It says "The ID token expires one hour after the user authenticates. You should not process the ID token in your client or web API after it has expired."
Which is the error I am seeing in my logs,
com.nimbusds.jwt.proc.BadJWTException: Expired JWT. I assume, that the JWT token has already expired, what would be my steps to successfully enforce token based authorization?

If you are using Java, The way I have programmed is this,
// Parse the Cognito Keys and get the key by kid
// Key is just a class that is used for parsing JSON to POJO
Key key = this.keyService.getKeyByKeyId(JWT.decode(token).getKeyId());
// Use Key's N and E
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(key.getN()));
BigInteger exponent = new BigInteger(1, Base64.decodeBase64(key.getE()));
// Create a publick key
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
} catch (InvalidKeySpecException e) {
// Throw error
} catch (NoSuchAlgorithmException e) {
// Throw error
}
// get an algorithm instance
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
// I verify ISS field of the token to make sure it's from the Cognito source
String iss = String.format("https://cognito-idp.%s.amazonaws.com/%s", REGION, POOL_ID);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(iss)
.withClaim("token_use", "id") // make sure you're verifying id token
.build();
// Verify the token
DecodedJWT jwt = verifier.verify(token);
// Parse various fields
String username = jwt.getClaim("sub").asString();
String email = jwt.getClaim("email").asString();
String phone = jwt.getClaim("phone_number").asString();
String[] groups = jwt.getClaim("cognito:groups").asArray(String.class);
I am using this repo to verify and parsing of the tokens,
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
Make sure you are importing the following,
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;
If the token is expired, it won't be varified.

You need the refresh token, that helps you get new identity and access tokens. The Cognito JS SDK refreshes the token automatically.
Now in your case, seems like you need to call the RefreshToken and add a check to see if the token is expired.
The identity/access tokens come with a expiration time so this is something you can do locally in your application before you use them.

Related

JWT private key and public key

I have asked to create a JWT token for an API. For this I have use the jose4j dependency to generate and consume that token. I've given an example token that I've debbuged in jwt.io and it seems that I need a public and private key, as you can see in the signature below:
What I've done so far is this, following the documentation in jose4j bitbucket page:
public String createJWT() throws JoseException, MalformedClaimException {
RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);;
rsaJsonWebKey.setKeyId(keyId);
JwtClaims claims = new JwtClaims();
claims.setJwtId(keyId);
claims.setIssuer(issuer); // who creates the token and signs it
claims.setAudience(audience); // to whom the token is intended to be sent
claims.setSubject(subject); // the subject/principal is whom the token is about
claims.setExpirationTimeMinutesInTheFuture(60); // 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)
// 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());
// The JWT is signed using the private key
jws.setKey(rsaJsonWebKey.getPrivateKey());
The thing is I don't know from where I'm suppose to get this private key, or what should it be.
I have also a keydata class with a username and a password, are those attributes my private key?
Then I have the consumer like this:
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
.setVerificationKey(rsaJsonWebKey.getKey())// verify the signature with the public key
.setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
.build(); // create the JwtConsumer instance
Where in this line: .setVerificationKey(rsaJsonWebKey.getKey())// verify the signature with the public key the code supponse to retrieve the public key.
So from where I'm suppose to get those key? Should I ask my collegues for them or what?

Unable to generate access token from grant token in zoho

I have registered the application and generate clientId, clientsceret, code from the application.
I have configured and initialize it but unable to generate access token.
This is my code:
ZohoOAuthClient client = ZohoOAuthClient.GetInstance();
string grantToken = "1000.fd54383a88527ee4a9dfd589f4bba161.95bdbba47dffb77a5d830b2561b2d7a3";
ZohoOAuthTokens tokens = client.GenerateAccessToken(grantToken);
string accessToken = tokens.AccessToken;
string refreshToken = tokens.RefreshToken;
I'm getting error of "error": "invalid_code"
You may need to initialize the RestClient before instantiating and calling to GenerateAccessToken
ZCRMRestClient.initialize();

Google API - Required 'compute.globalOperations.get' permission

I'm trying to modify an instance's tags list using the goolge compute engine API for Java. My pom.xml imports this dependency:
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-compute</artifactId>
<version>v1-rev173-1.23.0</version>
</dependency>
I can execute the action that will update the tags associated with a VM successfully:
public boolean setInstanceTags(Compute computeConnection, ArrayList<String> nwTags, String projectId, String zone, String instanceName) {
Instance instance = computeConnection.instances().get(projectId, zone, instanceName).execute();
Tags tagsToSet = new Tags();
tagsToSet.setFingerprint(instance.getTags().getFingerprint());
tagsToSet.setItems(new ArrayList<String>());
for (String tag: nwTags) {
tagsToSet.getItems().add(tag);
}
Operation setTagsOperation = computeConnection.instances().setTags(projectId, zone, instanceName, tagsToSet).execute();
In order to get feedback on whether that operation succeeded I would like to pull the operation status as follows:
String setTagsOperationId = setTagsOperation.getName();
setTagsOperation = computeConnection.globalOperations().get(projectId, setTagsOperationId).execute();
This throws this error:
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Required 'compute.globalOperations.get' permission for 'projects/myproject/global/operations/operation-1523604756600-569b5e04b94c3-a87939f4-4e293939'",
"reason" : "forbidden"
} ],
"message" : "Required 'compute.globalOperations.get' permission for 'projects/myproject/global/operations/operation-1523604756600-569b5e04b94c3-a87939f4-4e293939'"
But the service account I'm using does have the "Compute Admin" IAM role and my code is also setting the admin scope:
SCOPES = Arrays.asList(ComputeScopes.COMPUTE);
I'm using the same account/permissions to create firewall rules and pull the status on those operations successfully. Not sure why there is a difference in permissions for pulling operation status for instances.setTags operations and firewalls.insert.
The only hint I found is when pulling data on the firewalls.insert operation the 'selfLink' shows that the operation is located in the global scope:
"https://www.googleapis.com/compute/v1/projects/myproject/global/operations/operation-1523604247193-569b5c1eea5a8-2ccf40e9-8815af38"
where as the instances.setTags operation selfLink shows that this operation is located in a specific zone:
"https://www.googleapis.com/compute/v1/projects/myproject/zones/us-central1-c/operations/operation-1523604346365-569b5c7d7e449-dc64de03-fdb77847"
You can modify the tag of an instance from console and check that this operation is a zonal operation, not a global operation. Below selfLink can be found from Equivalent REST response from any modify tag operation.
"selfLink": "https://www.googleapis.com/compute/v1/projects/[project_name]/zones/us-central1-f/operations/operation-0000000000000-0000000000000-00000000-00000000",
I believe for this reason, it is needed to use a zonal method rather than global method. You can use the below method to resolve this issue.
Method: zoneOperations.get
That said, Compute Admin role already has all required permissions including compute.zoneOperations permission to work in this case.
I have tested the following API [1] call with a service account with role "Compute Admin":
$ curl -H"Authorization: Bearer "$(gcloud auth print-access-token) https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/operations/[OPERATION_NAME]
And it returned me the expected value with no error.
In order to know if your code and service account have the right role, you can try the following code extracted from the official documentation [2] with the value set to "compute.globalOperations.get".
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.cloudresourcemanager.CloudResourceManager;
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsRequest;
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
public class CloudResourceManagerExample {
public static void main(String args[]) throws IOException, GeneralSecurityException {
// REQUIRED: The resource for which the policy detail is being requested.
// See the operation documentation for the appropriate value for this field.
String resource = "my-resource"; // TODO: Update placeholder value.
// TODO: Assign values to desired fields of `requestBody`:
TestIamPermissionsRequest requestBody = new TestIamPermissionsRequest();
CloudResourceManager cloudResourceManagerService = createCloudResourceManagerService();
CloudResourceManager.Projects.TestIamPermissions request =
cloudResourceManagerService.projects().testIamPermissions(resource, requestBody);
TestIamPermissionsResponse response = request.execute();
// TODO: Change code below to process the `response` object:
System.out.println(response);
}
public static CloudResourceManager createCloudResourceManagerService()
throws IOException, GeneralSecurityException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential credential = GoogleCredential.getApplicationDefault();
if (credential.createScopedRequired()) {
credential =
credential.createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform"));
}
return new CloudResourceManager.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Google-CloudResourceManagerSample/0.1")
.build();
}
}
This way you will know if your code is using the correct service account or if there is an issue.
[1] https://cloud.google.com/compute/docs/reference/rest/beta/globalOperations/get
[2] https://cloud.google.com/resource-manager/reference/rest/v1/projects/testIamPermissions

Oauth2 Spring Security Resource Server and Independent Auth Server

everyone!
I'm new to Oauth2 and I've had different approaches with it.
I have a doubt. I'm actually building a Provider Server with Spring Security and I have an external access token provider (Google and AWS Cognito).
I know the process to get the access token following the code grant flow (Which is the one I want to implement). I built an Android app that gets the code and changes it for the access token.
My question is:
How do I validate that the token I'm sending to the Provider Server is a valid one using Spring Security to also access the protected resources that the server has?
Thank you in advance.
I think there are two alternatives either u get the public key and verify the token urself or maybe they have an endpoint where you can send the token and know if its a valid one or not.
Something like this
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
// Specify the CLIENT_ID of the app that accesses the backend:
.setAudience(Collections.singletonList(CLIENT_ID))
// Or, if multiple clients access the backend:
//.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
.build();
// (Receive idTokenString by HTTPS POST)
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
// Print user identifier
String userId = payload.getSubject();
System.out.println("User ID: " + userId);
// Get profile information from payload
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
// Use or store profile information
// ...
} else {
System.out.println("Invalid ID token.");
}
Link: https://developers.google.com/identity/sign-in/web/backend-auth

OAuth 2.0. No session? (stateless)

I'm going to implement OAuth 2.0 and REST API with it
to grant different permissions per users and also to scale well.
To scale well, stateless is easier because there is
NO file, database, in-memory based session with it.
Below is how I understand OAuth 2.
OAuth Server give an access token to a user.
The user's access token is stored in cookie.
When user access to REST API, user sends with the access token.
Server receives request with access token.
Server find out whether access token is valid and the user has permission to do request.
Do or reject based on user's privilege.
So I do not have to worry about session storage. Right?
What you are describing here, is the OAuth 2 Implicit Grant flow. OAuth 2 also includes three other flows, but as it seems that your ressource owner (the user) is initiating requests using browser side Javascript (you were talking about cookies), this is the flow you should go for.
On client side, OAuth only requires you to store the access_token for accessing protected ressources (and a refresh_token if you're going for an expiring access_token).
A more recent innovation is JWT - JSON Web Token.
Here is a link to the spec:
JWT - JSON Web Token
JWT is a method of using Hashed tokens using a Hashing method such as HMAC which stands for a Hash-based Message Authentication Code. Because the token is hashed using a secret key, the server can determine if the token has been tampered with.
Here is an example method to create a Hashed token for JWT:
public String createTokenForUser(User user) {
byte[] userBytes = toJSON(user);
byte[] hash = createHmac(userBytes);
final StringBuilder sb = new StringBuilder(170);
sb.append(toBase64(userBytes));
sb.append(SEPARATOR);
sb.append(toBase64(hash));
return sb.toString();
}
Here is an example of decoding a token to ensure it was not tampered with:
public User parseUserFromToken(String token) {
final String[] parts = token.split(SEPARATOR_SPLITTER);
if (parts.length == 2 && parts[0].length() > 0 && parts[1].length() > 0) {
try {
final byte[] userBytes = fromBase64(parts[0]);
final byte[] hash = fromBase64(parts[1]);
boolean validHash = Arrays.equals(createHmac(userBytes), hash);
if (validHash) {
final User user = fromJSON(userBytes);
if (new Date().getTime() < user.getExpires()) {
return user;
}
}
} catch (IllegalArgumentException e) {
//log tampering attempt here
}
}
return null;
}
Here is an article with a more complete example: Stateless Authentication

Resources