Skip to content

Commit e15d74d

Browse files
Add support to revoke access token when bound session is expired/invalid.
1 parent a4a81aa commit e15d74d

File tree

6 files changed

+288
-108
lines changed

6 files changed

+288
-108
lines changed

components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/bindings/impl/AbstractTokenBinder.java

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,19 @@
1818

1919
package org.wso2.carbon.identity.oauth2.token.bindings.impl;
2020

21-
import org.apache.commons.codec.digest.DigestUtils;
22-
import org.apache.commons.lang.ArrayUtils;
2321
import org.apache.commons.lang.StringUtils;
2422
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
2523
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCache;
2624
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheEntry;
2725
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheKey;
2826
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
29-
import org.wso2.carbon.identity.oauth2.model.HttpRequestHeader;
3027
import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinder;
3128
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
3229

33-
import java.net.HttpCookie;
3430
import java.util.Optional;
3531

3632
import javax.servlet.http.HttpServletRequest;
37-
import javax.ws.rs.core.HttpHeaders;
3833

39-
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.COMMONAUTH_COOKIE;
4034
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.AUTHORIZATION_CODE;
4135
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN;
4236

@@ -92,38 +86,12 @@ private boolean isValidTokenBinding(OAuth2AccessTokenReqDTO oAuth2AccessTokenReq
9286

9387
if (REFRESH_TOKEN.equals(oAuth2AccessTokenReqDTO.getGrantType())) {
9488

95-
HttpRequestHeader[] httpRequestHeaders = oAuth2AccessTokenReqDTO.getHttpRequestHeaders();
96-
if (ArrayUtils.isEmpty(httpRequestHeaders)) {
97-
return false;
98-
}
99-
100-
for (HttpRequestHeader httpRequestHeader : httpRequestHeaders) {
101-
if (HttpHeaders.COOKIE.equalsIgnoreCase(httpRequestHeader.getName())) {
102-
if (ArrayUtils.isEmpty(httpRequestHeader.getValue())) {
103-
return false;
104-
}
105-
106-
String[] cookies = httpRequestHeader.getValue()[0].split(";");
107-
String cookiePrefix = cookieName + "=";
108-
for (String cookie : cookies) {
109-
if (StringUtils.isNotBlank(cookie) && cookie.trim().startsWith(cookiePrefix) &&
110-
HttpCookie.parse(cookie).get(0) != null) {
111-
String cookieValue = HttpCookie.parse(cookie).get(0).getValue();
112-
if (StringUtils.isNotBlank(cookieValue)) {
113-
String tokenBindingValue;
114-
if (COMMONAUTH_COOKIE.equals(cookieName)) {
115-
// For sso-session binding,token binding value will be the session context id.
116-
tokenBindingValue = DigestUtils.sha256Hex(cookieValue);
117-
} else {
118-
tokenBindingValue = cookieValue;
119-
}
120-
String receivedBindingReference =
121-
OAuth2Util.getTokenBindingReference(tokenBindingValue);
122-
return bindingReference.equals(receivedBindingReference);
123-
}
124-
}
125-
}
126-
}
89+
Optional<String> tokenBindingValueOptional = OAuth2Util.getTokenBindingValue(oAuth2AccessTokenReqDTO,
90+
cookieName);
91+
if (tokenBindingValueOptional.isPresent()) {
92+
String tokenBindingValue = tokenBindingValueOptional.get();
93+
String receivedBindingReference = OAuth2Util.getTokenBindingReference(tokenBindingValue);
94+
return bindingReference.equals(receivedBindingReference);
12795
}
12896

12997
return false;

components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/bindings/impl/SSOSessionBasedTokenBinder.java

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
2-
* Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
2+
* Copyright (c) 2019-2025, WSO2 LLC. (http://www.wso2.com).
33
*
4-
* WSO2 Inc. licenses this file to you under the Apache License,
4+
* WSO2 LLC. licenses this file to you under the Apache License,
55
* Version 2.0 (the "License"); you may not use this file except
66
* in compliance with the License.
77
* You may obtain a copy of the License at
@@ -30,6 +30,7 @@
3030
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
3131
import org.wso2.carbon.identity.oauth2.OAuth2Constants;
3232
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
33+
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
3334

3435
import java.util.Arrays;
3536
import java.util.Collections;
@@ -128,20 +129,7 @@ public boolean isValidTokenBinding(Object request, String bindingReference) {
128129

129130
try {
130131
String sessionIdentifier = getTokenBindingValue((HttpServletRequest) request);
131-
if (StringUtils.isBlank(sessionIdentifier)) {
132-
if (log.isDebugEnabled()) {
133-
log.debug("CommonAuthId cookie is not found in the request.");
134-
}
135-
return false;
136-
}
137-
/* Retrieve session context information using sessionIdentifier in order to check the validity of
138-
commonAuthId cookie.*/
139-
SessionContext sessionContext = FrameworkUtils.getSessionContextFromCache(sessionIdentifier);
140-
if (sessionContext == null) {
141-
if (log.isDebugEnabled()) {
142-
log.debug("Session context is not found corresponding to the session identifier: " +
143-
sessionIdentifier);
144-
}
132+
if (!isSSOSessionValid(sessionIdentifier)) {
145133
return false;
146134
}
147135
} catch (OAuthSystemException e) {
@@ -154,6 +142,36 @@ public boolean isValidTokenBinding(Object request, String bindingReference) {
154142
@Override
155143
public boolean isValidTokenBinding(OAuth2AccessTokenReqDTO oAuth2AccessTokenReqDTO, String bindingReference) {
156144

157-
return isValidTokenBinding(oAuth2AccessTokenReqDTO, bindingReference, COMMONAUTH_COOKIE);
145+
Optional<String> sessionIdentifier =
146+
OAuth2Util.getTokenBindingValue(oAuth2AccessTokenReqDTO, COMMONAUTH_COOKIE);
147+
if (isSSOSessionValid(sessionIdentifier.orElse(null))) {
148+
return isValidTokenBinding(oAuth2AccessTokenReqDTO, bindingReference, COMMONAUTH_COOKIE);
149+
}
150+
return false;
151+
}
152+
153+
/**
154+
* Check whether the session identified by the session identifier is valid.
155+
*
156+
* @param sessionIdentifier Session identifier.
157+
* @return True if the session is valid.
158+
*/
159+
private boolean isSSOSessionValid(String sessionIdentifier) {
160+
161+
if (StringUtils.isBlank(sessionIdentifier)) {
162+
if (log.isDebugEnabled()) {
163+
log.debug("Invalid session identifier. CommonAuthId cookie is not found in the request.");
164+
}
165+
return false;
166+
}
167+
SessionContext sessionContext = FrameworkUtils.getSessionContextFromCache(sessionIdentifier);
168+
if (sessionContext == null) {
169+
if (log.isDebugEnabled()) {
170+
log.debug("Session context is not found corresponding to the session identifier: " +
171+
sessionIdentifier);
172+
}
173+
return false;
174+
}
175+
return true;
158176
}
159177
}

components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException;
5757
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
5858
import org.wso2.carbon.identity.oauth2.IdentityOAuth2ServerException;
59+
import org.wso2.carbon.identity.oauth2.OAuth2Constants;
5960
import org.wso2.carbon.identity.oauth2.ResponseHeader;
6061
import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory;
6162
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
@@ -118,7 +119,20 @@ public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx)
118119
.validateRefreshToken(tokReqMsgCtx);
119120

120121
validateRefreshTokenInRequest(tokenReq, validationBean);
121-
validateTokenBindingReference(tokenReq, validationBean);
122+
123+
TokenBinding tokenBinding = null;
124+
if (StringUtils.isNotBlank(validationBean.getTokenBindingReference()) && !NONE
125+
.equals(validationBean.getTokenBindingReference())) {
126+
Optional<TokenBinding> tokenBindingOptional = OAuthTokenPersistenceFactory.getInstance()
127+
.getTokenBindingMgtDAO()
128+
.getTokenBindingByBindingRef(validationBean.getTokenId(),
129+
validationBean.getTokenBindingReference());
130+
if (tokenBindingOptional.isPresent()) {
131+
tokenBinding = tokenBindingOptional.get();
132+
tokReqMsgCtx.setTokenBinding(tokenBinding);
133+
}
134+
}
135+
validateTokenBindingReference(tokenReq, validationBean, tokenBinding);
122136
validateAuthenticatedUser(validationBean, tokReqMsgCtx);
123137

124138
if (log.isDebugEnabled()) {
@@ -276,14 +290,6 @@ private void setPropertiesForTokenGeneration(OAuthTokenReqMessageContext tokReqM
276290
tokReqMsgCtx.setScope(validationBean.getScope());
277291
tokReqMsgCtx.getOauth2AccessTokenReqDTO().setAccessTokenExtendedAttributes(
278292
validationBean.getAccessTokenExtendedAttributes());
279-
if (StringUtils.isNotBlank(validationBean.getTokenBindingReference()) && !NONE
280-
.equals(validationBean.getTokenBindingReference())) {
281-
Optional<TokenBinding> tokenBindingOptional = OAuthTokenPersistenceFactory.getInstance()
282-
.getTokenBindingMgtDAO()
283-
.getTokenBindingByBindingRef(validationBean.getTokenId(),
284-
validationBean.getTokenBindingReference());
285-
tokenBindingOptional.ifPresent(tokReqMsgCtx::setTokenBinding);
286-
}
287293
// Store the old access token as a OAuthTokenReqMessageContext property, this is already
288294
// a preprocessed token.
289295
tokReqMsgCtx.addProperty(PREV_ACCESS_TOKEN, validationBean);
@@ -959,11 +965,11 @@ private boolean isRenewRefreshToken(String renewRefreshToken) {
959965
}
960966

961967
private void validateTokenBindingReference(OAuth2AccessTokenReqDTO tokenReqDTO,
962-
RefreshTokenValidationDataDO validationDataDO)
968+
RefreshTokenValidationDataDO validationDataDO,
969+
TokenBinding tokenBinding)
963970
throws IdentityOAuth2Exception {
964971

965-
if (StringUtils.isBlank(validationDataDO.getTokenBindingReference()) || NONE
966-
.equals(validationDataDO.getTokenBindingReference())) {
972+
if (tokenBinding == null) {
967973
return;
968974
}
969975

@@ -979,6 +985,18 @@ private void validateTokenBindingReference(OAuth2AccessTokenReqDTO tokenReqDTO,
979985
return;
980986
}
981987

988+
// Validate SSO session bound token.
989+
if (OAuth2Constants.TokenBinderType.SSO_SESSION_BASED_TOKEN_BINDER.equals(oAuthAppDO.getTokenBindingType())) {
990+
if (!OAuth2Util.isTokenBoundToActiveSSOSession(tokenBinding, validationDataDO.getAccessToken(), oAuthAppDO,
991+
validationDataDO.getAuthorizedUser())) {
992+
if (log.isDebugEnabled()) {
993+
log.debug("Token is not bound to an active SSO session.");
994+
}
995+
throw new IdentityOAuth2Exception("Token binding validation failed. Token is not bound to an" +
996+
"active SSO session.");
997+
}
998+
}
999+
9821000
Optional<TokenBinder> tokenBinderOptional = OAuth2ServiceComponentHolder.getInstance()
9831001
.getTokenBinder(oAuthAppDO.getTokenBindingType());
9841002
if (!tokenBinderOptional.isPresent()) {

0 commit comments

Comments
 (0)