Skip to content

Commit

Permalink
fix: refresh token check in token exchange
Browse files Browse the repository at this point in the history
  • Loading branch information
sattvikc committed Sep 19, 2024
1 parent a529072 commit 8e34a75
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 41 deletions.
2 changes: 1 addition & 1 deletion src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -3063,7 +3063,7 @@ public void revoke(AppIdentifier appIdentifier, String targetType, String target
}

@Override
public boolean isRevoked(AppIdentifier appIdentifier, String targetType, String targetValue, long issuedAt)
public boolean isRevoked(AppIdentifier appIdentifier, String[] targetTypes, String[] targetValues, long issuedAt)
throws StorageQueryException {
throw new IllegalStateException("todo implement");
}
Expand Down
66 changes: 31 additions & 35 deletions src/main/java/io/supertokens/oauth/OAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -380,22 +380,9 @@ public static void verifyAndUpdateIntrospectRefreshTokenPayload(Main main, AppId

Transformations.transformExt(payload);

long issuedAt = payload.get("iat").getAsLong();
String subject = payload.get("sub").getAsString();
String clientId = payload.get("client_id").getAsString();
String sessionHandle = null;
if (payload.has("sessionHandle")) {
sessionHandle = payload.get("sessionHandle").getAsString();
}
boolean isValid = !isTokenRevokedBasedOnPayload(oauthStorage, appIdentifier, payload);

boolean isSubjectValid = !oauthStorage.isRevoked(appIdentifier, "sub", subject, issuedAt);
boolean isClientIdSubjectValid = !oauthStorage.isRevoked(appIdentifier, "client_id_sub", clientId + ":" + subject, issuedAt);
boolean isSessionHandleValid = true;
if (sessionHandle != null) {
isSessionHandleValid = !oauthStorage.isRevoked(appIdentifier, "sessionHandle", sessionHandle, issuedAt);
}

if (isSubjectValid && isClientIdSubjectValid && isSessionHandleValid) {
if (isValid) {
payload.addProperty("iss", iss);
} else {
payload.entrySet().clear();
Expand All @@ -421,6 +408,32 @@ public static void verifyAndUpdateIntrospectRefreshTokenPayload(Main main, AppId
}
}

private static boolean isTokenRevokedBasedOnPayload(OAuthStorage oauthStorage, AppIdentifier appIdentifier, JsonObject payload) throws StorageQueryException {
long issuedAt = payload.get("iat").getAsLong();
List<String> targetTypes = new ArrayList<>();
List<String> targetValues = new ArrayList<>();

targetTypes.add("client_id");
targetValues.add(payload.get("client_id").getAsString());

if (payload.has("jti")) {
targetTypes.add("jti");
targetValues.add(payload.get("jti").getAsString());
}

if (payload.has("rt_hash")) {
targetTypes.add("rt_hash");
targetValues.add(payload.get("rt_hash").getAsString());
}

if (payload.has("sessionHandle")) {
targetTypes.add("session_handle");
targetValues.add(payload.get("sessionHandle").getAsString());
}

return oauthStorage.isRevoked(appIdentifier, targetTypes.toArray(new String[0]), targetValues.toArray(new String[0]), issuedAt);
}


public static JsonObject introspectAccessToken(Main main, AppIdentifier appIdentifier, Storage storage,
String token) throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException, UnsupportedJWTSigningAlgorithmException {
Expand All @@ -430,26 +443,9 @@ public static JsonObject introspectAccessToken(Main main, AppIdentifier appIdent

if (payload.has("stt") && payload.get("stt").getAsInt() == OAuthToken.TokenType.ACCESS_TOKEN.getValue()) {

long issuedAt = payload.get("iat").getAsLong();
String rtHash = payload.get("rt_hash").getAsString();
String subject = payload.get("sub").getAsString();
String jti = payload.get("jti").getAsString();
String clientId = payload.get("client_id").getAsString();
String sessionHandle = null;
if (payload.has("sessionHandle")) {
sessionHandle = payload.get("sessionHandle").getAsString();
}

boolean isRTHashValid = !oauthStorage.isRevoked(appIdentifier, "rt_hash", rtHash, issuedAt);
boolean isSubjectValid = !oauthStorage.isRevoked(appIdentifier, "sub", subject, issuedAt);
boolean isJTIValid = !oauthStorage.isRevoked(appIdentifier, "jti", jti, issuedAt);
boolean isClientIdSubjectValid = !oauthStorage.isRevoked(appIdentifier, "client_id_sub", clientId + ":" + subject, issuedAt);
boolean isSessionHandleValid = true;
if (sessionHandle != null) {
isSessionHandleValid = !oauthStorage.isRevoked(appIdentifier, "sessionHandle", sessionHandle, issuedAt);
}
boolean isValid = !isTokenRevokedBasedOnPayload(oauthStorage, appIdentifier, payload);

if (isRTHashValid && isSubjectValid && isJTIValid && isClientIdSubjectValid && isSessionHandleValid) {
if (isValid) {
payload.addProperty("active", true);
payload.addProperty("token_type", "Bearer");
payload.addProperty("token_use", "access_token");
Expand Down Expand Up @@ -498,6 +494,6 @@ public static void revokeAccessToken(Main main, AppIdentifier appIdentifier,
public static void revokeSessionHandle(Main main, AppIdentifier appIdentifier, Storage storage,
String sessionHandle) throws StorageQueryException {
OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage);
oauthStorage.revoke(appIdentifier, "sessionHandle", sessionHandle);
oauthStorage.revoke(appIdentifier, "session_handle", sessionHandle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private static void handleOAuthClientNotFoundException(HttpServletResponse resp)
resp.getWriter().println(response.toString());
}

private static void handleOAuthAPIException(HttpServletResponse resp, OAuthAPIException e) throws IOException {
public static void handleOAuthAPIException(HttpServletResponse resp, OAuthAPIException e) throws IOException {
JsonObject response = new JsonObject();
response.addProperty("status", "OAUTH_ERROR");
response.addProperty("error", e.error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import com.auth0.jwt.exceptions.JWTCreationException;
import com.google.gson.*;
import io.supertokens.Main;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException;
import io.supertokens.multitenancy.exception.BadPermissionException;
import io.supertokens.oauth.HttpRequestForOry;
import io.supertokens.oauth.OAuth;
import io.supertokens.oauth.exceptions.OAuthAPIException;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
Expand Down Expand Up @@ -61,6 +63,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
String iss = InputParser.parseStringOrThrowError(input, "iss", false); // input validation
JsonObject bodyFromSDK = InputParser.parseJsonObjectOrThrowError(input, "inputBody", false);

String grantType = InputParser.parseStringOrThrowError(bodyFromSDK, "grant_type", false);
JsonObject accessTokenUpdate = InputParser.parseJsonObjectOrThrowError(input, "access_token", true);
JsonObject idTokenUpdate = InputParser.parseJsonObjectOrThrowError(input, "id_token", true);

Expand All @@ -73,7 +76,49 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
formFields.put(entry.getKey(), entry.getValue().getAsString());
}


try {
AppIdentifier appIdentifier = getAppIdentifier(req);
Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req);

// check if the refresh token is valid
if (grantType.equals("refresh_token")) {
String refreshToken = InputParser.parseStringOrThrowError(bodyFromSDK, "refresh_token", false);

HttpRequestForOry.Response response = OAuthProxyHelper.proxyFormPOST(
main, req, resp,
appIdentifier,
storage,
null, // clientIdToCheck
"/admin/oauth2/introspect", // pathProxy
true, // proxyToAdmin
false, // camelToSnakeCaseConversion
formFields,
new HashMap<>() // headers
);

if (response == null) {
return; // proxy helper would have sent the error response
}

JsonObject refreshTokenPayload = response.jsonResponse.getAsJsonObject();

try {
OAuth.verifyAndUpdateIntrospectRefreshTokenPayload(main, appIdentifier, storage, refreshTokenPayload, iss, refreshToken);
} catch (StorageQueryException | TenantOrAppNotFoundException |
FeatureNotEnabledException | InvalidConfigException e) {
throw new ServletException(e);
}

if (!refreshTokenPayload.get("active").getAsBoolean()) {
// this is what ory would return for an invalid token
OAuthProxyHelper.handleOAuthAPIException(resp, new OAuthAPIException(
"token_inactive", "Token is inactive because it is malformed, expired or otherwise invalid. Token validation failed.", 401
));
return;
}
}

HttpRequestForOry.Response response = OAuthProxyHelper.proxyFormPOST(
main, req, resp,
getAppIdentifier(req),
Expand All @@ -88,11 +133,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I

if (response != null) {
try {
AppIdentifier appIdentifier = getAppIdentifier(req);
Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req);

response.jsonResponse = OAuth.transformTokens(super.main, appIdentifier, storage, response.jsonResponse.getAsJsonObject(), iss, accessTokenUpdate, idTokenUpdate, useDynamicKey);
} catch (IOException | InvalidConfigException | TenantOrAppNotFoundException | BadPermissionException | StorageQueryException | InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException | JWTCreationException | JWTException | StorageTransactionLogicException | UnsupportedJWTSigningAlgorithmException e) {
} catch (IOException | InvalidConfigException | TenantOrAppNotFoundException | StorageQueryException | InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException | JWTCreationException | JWTException | StorageTransactionLogicException | UnsupportedJWTSigningAlgorithmException e) {
throw new ServletException(e);
}

Expand Down

0 comments on commit 8e34a75

Please sign in to comment.