001package com.nimbusds.openid.connect.sdk; 002 003 004import net.jcip.annotations.Immutable; 005 006import net.minidev.json.JSONObject; 007 008import com.nimbusds.jwt.JWT; 009import com.nimbusds.jwt.JWTParser; 010 011import com.nimbusds.oauth2.sdk.AccessTokenResponse; 012import com.nimbusds.oauth2.sdk.ParseException; 013import com.nimbusds.oauth2.sdk.SerializeException; 014import com.nimbusds.oauth2.sdk.http.HTTPResponse; 015import com.nimbusds.oauth2.sdk.token.AccessToken; 016import com.nimbusds.oauth2.sdk.token.RefreshToken; 017import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 018 019 020/** 021 * OpenID Connect access token response. This class is immutable. 022 * 023 * <p>Example HTTP response: 024 * 025 * <pre> 026 * HTTP/1.1 200 OK 027 * Content-Type: application/json 028 * Cache-Control: no-store 029 * Pragma: no-cache 030 * 031 * { 032 * "access_token" : "SlAV32hkKG", 033 * "token_type" : "Bearer", 034 * "refresh_token" : "8xLOxBtZp8", 035 * "expires_in" : 3600, 036 * "id_token" : "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9zZXJ2Z 037 * XIuZXhhbXBsZS5jb20iLCJ1c2VyX2lkIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIjoic 038 * zZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiZXhwIjoxMzExMjgxO 039 * TcwLCJpYXQiOjEzMTEyODA5NzB9.RgXxzppVvn1EjUiV3LIZ19SyhdyREe_2jJjW 040 * 5EC8XjNuJfe7Dte8YxRXxssJ67N8MT9mvOI3HOHm4whNx5FCyemyCGyTLHODCeAr 041 * _id029-4JP0KWySoan1jmT7vbGHhu89-l9MTdaEvu7pNZO7DHGwqnMWRe8hdG7jU 042 * ES4w4ReQTygKwXVVOaiGoeUrv6cZdbyOnpGlRlHaiOsv_xMunNVJtn5dLz-0zZwV 043 * ftKVpFuc1pGaVsyZsOtkT32E4c6MDHeCvIDlR5ESC0ct8BLvGJDB5954MjCR4_X2 044 * GAEHonKw4NF8wTmUFvhslYXmjRNFs21Byjn3jNb7lSa3MBfVsw" 045 * } 046 * </pre> 047 * 048 * <p>Related specifications: 049 * 050 * <ul> 051 * <li>OpenID Connect Messages 1.0, section 2.2.3. 052 * <li>OpenID Connect Standard 1.0, section 3.1.2. 053 * <li>OAuth 2.0 (RFC 6749), sections 4.1.4 and 5.1. 054 * </ul> 055 * 056 * @author Vladimir Dzhuvinov 057 */ 058@Immutable 059public final 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 super(accessToken, refreshToken); 102 103 this.idToken = idToken; 104 105 idTokenString = null; 106 } 107 108 109 /** 110 * Creates a new OpenID Connect access token response. 111 * 112 * @param accessToken The access token. Must not be {@code null}. 113 * @param refreshToken Optional refresh token, {@code null} if none. 114 * @param idTokenString The ID token string. Must be {@code null} if 115 * the request grant type was not 116 * {@link com.nimbusds.oauth2.sdk.GrantType#AUTHORIZATION_CODE}. 117 */ 118 public OIDCAccessTokenResponse(final AccessToken accessToken, 119 final RefreshToken refreshToken, 120 final String idTokenString) { 121 122 super(accessToken, refreshToken); 123 124 idToken = null; 125 126 this.idTokenString = idTokenString; 127 } 128 129 130 /** 131 * Gets the ID token. 132 * 133 * @return The ID token, {@code null} if none or if parsing to a JWT 134 * failed. 135 */ 136 public JWT getIDToken() { 137 138 if (idToken != null) 139 return idToken; 140 141 if (idTokenString != null) { 142 143 try { 144 return JWTParser.parse(idTokenString); 145 146 } catch (java.text.ParseException e) { 147 148 return null; 149 } 150 } 151 152 return null; 153 } 154 155 156 /** 157 * Gets the ID token string. 158 * 159 * @return The ID token string, {@code null} if none or if 160 * serialisation to a string failed. 161 */ 162 public String getIDTokenString() { 163 164 if (idTokenString != null) 165 return idTokenString; 166 167 if (idToken != null) { 168 169 // Reproduce originally parsed string if any 170 if (idToken.getParsedString() != null) 171 return idToken.getParsedString(); 172 173 try { 174 return idToken.serialize(); 175 176 } catch(IllegalStateException e) { 177 178 return null; 179 } 180 } 181 182 return null; 183 } 184 185 186 /** 187 * Returns the JSON object representing this OpenID Connect access 188 * token response. 189 * 190 * <p>Example JSON object: 191 * 192 * <pre> 193 * { 194 * "access_token" : "SlAV32hkKG", 195 * "token_type" : "Bearer", 196 * "refresh_token": "8xLOxBtZp8", 197 * "expires_in" : 3600, 198 * "id_token" : "eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso" 199 * } 200 * </pre> 201 * 202 * @return The JSON object. 203 * 204 * @throws SerializeException If this OpenID Connect access token 205 * response couldn't be serialised to a JSON 206 * object. 207 */ 208 @Override 209 public JSONObject toJSONObject() 210 throws SerializeException { 211 212 JSONObject o = super.toJSONObject(); 213 214 String idTokenOut = getIDTokenString(); 215 216 if (idTokenOut != null) 217 o.put("id_token", idTokenOut); 218 219 return o; 220 } 221 222 223 /** 224 * Parses an OpenID Connect access token response from the specified 225 * JSON object. 226 * 227 * @param jsonObject The JSON object to parse. Must not be 228 * {@code null}. 229 * 230 * @return The OpenID Connect access token response. 231 * 232 * @throws ParseException If the JSON object couldn't be parsed to an 233 * OpenID Connect access token response. 234 */ 235 public static OIDCAccessTokenResponse parse(final JSONObject jsonObject) 236 throws ParseException { 237 238 AccessTokenResponse atr = AccessTokenResponse.parse(jsonObject); 239 240 JWT idToken = null; 241 242 if (jsonObject.containsKey("id_token")) { 243 244 try { 245 idToken = JWTParser.parse(JSONObjectUtils.getString(jsonObject, "id_token")); 246 247 } catch (java.text.ParseException e) { 248 249 throw new ParseException("Couldn't parse ID token: " + e.getMessage(), e); 250 } 251 } 252 253 254 return new OIDCAccessTokenResponse(atr.getAccessToken(), 255 atr.getRefreshToken(), 256 idToken); 257 } 258 259 260 /** 261 * Parses an OpenID Connect access token response from the specified 262 * HTTP response. 263 * 264 * @param httpResponse The HTTP response. Must not be {@code null}. 265 * 266 * @return The OpenID Connect access token response. 267 * 268 * @throws ParseException If the HTTP response couldn't be parsed to an 269 * OpenID Connect access token response. 270 */ 271 public static OIDCAccessTokenResponse parse(final HTTPResponse httpResponse) 272 throws ParseException { 273 274 httpResponse.ensureStatusCode(HTTPResponse.SC_OK); 275 276 JSONObject jsonObject = httpResponse.getContentAsJSONObject(); 277 278 return parse(jsonObject); 279 } 280}