001package com.nimbusds.openid.connect.sdk; 002 003 004import java.util.HashMap; 005import java.util.Map; 006 007import net.jcip.annotations.Immutable; 008 009import net.minidev.json.JSONObject; 010 011import com.nimbusds.jwt.JWT; 012import com.nimbusds.jwt.JWTParser; 013 014import com.nimbusds.oauth2.sdk.AccessTokenResponse; 015import com.nimbusds.oauth2.sdk.ParseException; 016import com.nimbusds.oauth2.sdk.SerializeException; 017import com.nimbusds.oauth2.sdk.http.HTTPResponse; 018import com.nimbusds.oauth2.sdk.token.AccessToken; 019import com.nimbusds.oauth2.sdk.token.RefreshToken; 020import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 021 022 023/** 024 * OpenID Connect access token response with an optional ID token. 025 * 026 * <p>Example HTTP response: 027 * 028 * <pre> 029 * HTTP/1.1 200 OK 030 * Content-Type: application/json 031 * Cache-Control: no-store 032 * Pragma: no-cache 033 * 034 * { 035 * "access_token" : "SlAV32hkKG", 036 * "token_type" : "Bearer", 037 * "refresh_token" : "8xLOxBtZp8", 038 * "expires_in" : 3600, 039 * "id_token" : "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9zZXJ2Z 040 * XIuZXhhbXBsZS5jb20iLCJ1c2VyX2lkIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIjoic 041 * zZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiZXhwIjoxMzExMjgxO 042 * TcwLCJpYXQiOjEzMTEyODA5NzB9.RgXxzppVvn1EjUiV3LIZ19SyhdyREe_2jJjW 043 * 5EC8XjNuJfe7Dte8YxRXxssJ67N8MT9mvOI3HOHm4whNx5FCyemyCGyTLHODCeAr 044 * _id029-4JP0KWySoan1jmT7vbGHhu89-l9MTdaEvu7pNZO7DHGwqnMWRe8hdG7jU 045 * ES4w4ReQTygKwXVVOaiGoeUrv6cZdbyOnpGlRlHaiOsv_xMunNVJtn5dLz-0zZwV 046 * ftKVpFuc1pGaVsyZsOtkT32E4c6MDHeCvIDlR5ESC0ct8BLvGJDB5954MjCR4_X2 047 * GAEHonKw4NF8wTmUFvhslYXmjRNFs21Byjn3jNb7lSa3MBfVsw" 048 * } 049 * </pre> 050 * 051 * <p>Related specifications: 052 * 053 * <ul> 054 * <li>OpenID Connect Core 1.0, section 3.1.3.3. 055 * <li>OAuth 2.0 (RFC 6749), sections 4.1.4 and 5.1. 056 * </ul> 057 */ 058@Immutable 059public class OIDCAccessTokenResponse 060 extends AccessTokenResponse { 061 062 063 /** 064 * Optional ID Token serialised to a JWT. 065 */ 066 private final JWT idToken; 067 068 069 /** 070 * Optional ID Token as raw string (for more efficient serialisation). 071 */ 072 private final String idTokenString; 073 074 075 /** 076 * Creates a new OpenID Connect access token response with no ID token. 077 * 078 * @param accessToken The access token. Must not be {@code null}. 079 * @param refreshToken Optional refresh token, {@code null} if none. 080 */ 081 public OIDCAccessTokenResponse(final AccessToken accessToken, 082 final RefreshToken refreshToken) { 083 084 this(accessToken, refreshToken, (String)null); 085 } 086 087 088 /** 089 * Creates a new OpenID Connect access token response. 090 * 091 * @param accessToken The access token. Must not be {@code null}. 092 * @param refreshToken Optional refresh token, {@code null} if none. 093 * @param idToken The ID token. Must be {@code null} if the 094 * request grant type was not 095 * {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}. 096 */ 097 public OIDCAccessTokenResponse(final AccessToken accessToken, 098 final RefreshToken refreshToken, 099 final JWT idToken) { 100 101 this(accessToken, refreshToken, idToken, null); 102 } 103 104 105 /** 106 * Creates a new OpenID Connect access token response. 107 * 108 * @param accessToken The access token. Must not be {@code null}. 109 * @param refreshToken Optional refresh token, {@code null} if none. 110 * @param idToken The ID token. Must be {@code null} if the 111 * request grant type was not 112 * {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}. 113 * @param customParams Optional custom parameters, {@code null} if 114 * none. 115 */ 116 public OIDCAccessTokenResponse(final AccessToken accessToken, 117 final RefreshToken refreshToken, 118 final JWT idToken, 119 final Map<String,Object> customParams) { 120 121 super(accessToken, refreshToken, customParams); 122 123 this.idToken = idToken; 124 125 idTokenString = null; 126 } 127 128 129 /** 130 * Creates a new OpenID Connect access token response. 131 * 132 * @param accessToken The access token. Must not be {@code null}. 133 * @param refreshToken Optional refresh token, {@code null} if none. 134 * @param idTokenString The ID token string. Must be {@code null} if 135 * the request grant type was not 136 * {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}. 137 */ 138 public OIDCAccessTokenResponse(final AccessToken accessToken, 139 final RefreshToken refreshToken, 140 final String idTokenString) { 141 142 this(accessToken, refreshToken, idTokenString, null); 143 } 144 145 146 /** 147 * Creates a new OpenID Connect access token response. 148 * 149 * @param accessToken The access token. Must not be {@code null}. 150 * @param refreshToken Optional refresh token, {@code null} if none. 151 * @param idTokenString The ID token string. Must be {@code null} if 152 * the request grant type was not 153 * {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}. 154 * @param customParams Optional custom parameters, {@code null} if 155 * none. 156 */ 157 public OIDCAccessTokenResponse(final AccessToken accessToken, 158 final RefreshToken refreshToken, 159 final String idTokenString, 160 final Map<String,Object> customParams) { 161 162 super(accessToken, refreshToken, customParams); 163 164 idToken = null; 165 166 this.idTokenString = idTokenString; 167 } 168 169 170 /** 171 * Gets the ID token. 172 * 173 * @return The ID token, {@code null} if none or if parsing to a JWT 174 * failed. 175 */ 176 public JWT getIDToken() { 177 178 if (idToken != null) 179 return idToken; 180 181 if (idTokenString != null) { 182 183 try { 184 return JWTParser.parse(idTokenString); 185 186 } catch (java.text.ParseException e) { 187 188 return null; 189 } 190 } 191 192 return null; 193 } 194 195 196 /** 197 * Gets the ID token string. 198 * 199 * @return The ID token string, {@code null} if none or if 200 * serialisation to a string failed. 201 */ 202 public String getIDTokenString() { 203 204 if (idTokenString != null) 205 return idTokenString; 206 207 if (idToken != null) { 208 209 // Reproduce originally parsed string if any 210 if (idToken.getParsedString() != null) 211 return idToken.getParsedString(); 212 213 try { 214 return idToken.serialize(); 215 216 } catch(IllegalStateException e) { 217 218 return null; 219 } 220 } 221 222 return null; 223 } 224 225 226 /** 227 * Returns the JSON object representing this OpenID Connect access 228 * token response. 229 * 230 * <p>Example JSON object: 231 * 232 * <pre> 233 * { 234 * "access_token" : "SlAV32hkKG", 235 * "token_type" : "Bearer", 236 * "refresh_token": "8xLOxBtZp8", 237 * "expires_in" : 3600, 238 * "id_token" : "eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso" 239 * } 240 * </pre> 241 * 242 * @return The JSON object. 243 * 244 * @throws SerializeException If this OpenID Connect access token 245 * response couldn't be serialised to a JSON 246 * object. 247 */ 248 @Override 249 public JSONObject toJSONObject() 250 throws SerializeException { 251 252 JSONObject o = super.toJSONObject(); 253 254 String idTokenOut = getIDTokenString(); 255 256 if (idTokenOut != null) 257 o.put("id_token", idTokenOut); 258 259 return o; 260 } 261 262 263 /** 264 * Parses an OpenID Connect access token response from the specified 265 * JSON object. 266 * 267 * @param jsonObject The JSON object to parse. Must not be 268 * {@code null}. 269 * 270 * @return The OpenID Connect access token response. 271 * 272 * @throws ParseException If the JSON object couldn't be parsed to an 273 * OpenID Connect access token response. 274 */ 275 public static OIDCAccessTokenResponse parse(final JSONObject jsonObject) 276 throws ParseException { 277 278 AccessTokenResponse atr = AccessTokenResponse.parse(jsonObject); 279 280 JWT idToken = null; 281 282 if (jsonObject.containsKey("id_token")) { 283 284 try { 285 idToken = JWTParser.parse(JSONObjectUtils.getString(jsonObject, "id_token")); 286 287 } catch (java.text.ParseException e) { 288 289 throw new ParseException("Couldn't parse ID token: " + e.getMessage(), e); 290 } 291 } 292 293 // Parse the custom parameters 294 Map<String,Object> customParams = new HashMap<>(); 295 customParams.putAll(atr.getCustomParams()); 296 customParams.remove("id_token"); 297 298 return new OIDCAccessTokenResponse(atr.getAccessToken(), 299 atr.getRefreshToken(), 300 idToken, 301 customParams); 302 } 303 304 305 /** 306 * Parses an OpenID Connect access token response from the specified 307 * HTTP response. 308 * 309 * @param httpResponse The HTTP response. Must not be {@code null}. 310 * 311 * @return The OpenID Connect access token response. 312 * 313 * @throws ParseException If the HTTP response couldn't be parsed to an 314 * OpenID Connect access token response. 315 */ 316 public static OIDCAccessTokenResponse parse(final HTTPResponse httpResponse) 317 throws ParseException { 318 319 httpResponse.ensureStatusCode(HTTPResponse.SC_OK); 320 321 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 322 323 return parse(jsonObject); 324 } 325}