From 8e34a75f738503229ff41743a25e7af82e6c51f0 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 19 Sep 2024 14:16:25 +0530 Subject: [PATCH] fix: refresh token check in token exchange --- .../java/io/supertokens/inmemorydb/Start.java | 2 +- src/main/java/io/supertokens/oauth/OAuth.java | 66 +++++++++---------- .../webserver/api/oauth/OAuthProxyHelper.java | 2 +- .../webserver/api/oauth/OAuthTokenAPI.java | 50 ++++++++++++-- 4 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index be0cea257..4405fbb54 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -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"); } diff --git a/src/main/java/io/supertokens/oauth/OAuth.java b/src/main/java/io/supertokens/oauth/OAuth.java index 525f75f67..408817149 100644 --- a/src/main/java/io/supertokens/oauth/OAuth.java +++ b/src/main/java/io/supertokens/oauth/OAuth.java @@ -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(); @@ -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 targetTypes = new ArrayList<>(); + List 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 { @@ -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"); @@ -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); } } diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthProxyHelper.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthProxyHelper.java index 7f821973f..6764ae05d 100644 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthProxyHelper.java +++ b/src/main/java/io/supertokens/webserver/api/oauth/OAuthProxyHelper.java @@ -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); diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthTokenAPI.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthTokenAPI.java index 22a479c6a..8c3dce777 100644 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthTokenAPI.java +++ b/src/main/java/io/supertokens/webserver/api/oauth/OAuthTokenAPI.java @@ -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; @@ -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); @@ -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), @@ -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); }