001package com.nimbusds.openid.connect.sdk; 002 003 004import java.net.URI; 005import java.net.URISyntaxException; 006import java.net.URL; 007import java.util.Map; 008 009import net.jcip.annotations.Immutable; 010 011import com.nimbusds.jwt.JWT; 012import com.nimbusds.jwt.JWTParser; 013 014import com.nimbusds.oauth2.sdk.AuthorizationCode; 015import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse; 016import com.nimbusds.oauth2.sdk.ParseException; 017import com.nimbusds.oauth2.sdk.ResponseType; 018import com.nimbusds.oauth2.sdk.SerializeException; 019import com.nimbusds.oauth2.sdk.id.State; 020import com.nimbusds.oauth2.sdk.http.HTTPResponse; 021import com.nimbusds.oauth2.sdk.token.AccessToken; 022import com.nimbusds.oauth2.sdk.util.URIUtils; 023import com.nimbusds.oauth2.sdk.util.URLUtils; 024 025 026/** 027 * OpenID Connect authentication success response. Used to return an 028 * authorisation code, access token and / or ID Token at the Authorisation 029 * endpoint. 030 * 031 * <p>Example HTTP response with code and ID Token (code flow): 032 * 033 * <pre> 034 * HTTP/1.1 302 Found 035 * Location: https://client.example.org/cb# 036 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 037 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 038 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 039 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 040 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 041 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 042 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 043 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 044 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 045 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 046 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 047 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 048 * </pre> 049 * 050 * <p>Related specifications: 051 * 052 * <ul> 053 * <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6, 054 * 3.3.2.5 and 3.3.2.6. 055 * </ul> 056 */ 057@Immutable 058public class AuthenticationSuccessResponse 059 extends AuthorizationSuccessResponse 060 implements AuthenticationResponse { 061 062 063 /** 064 * The ID token, if requested. 065 */ 066 private final JWT idToken; 067 068 069 /** 070 * Creates a new OpenID Connect authentication success response. 071 * 072 * @param redirectURI The requested redirection URI. Must not be 073 * {@code null}. 074 * @param code The authorisation code, {@code null} if not 075 * requested. 076 * @param idToken The ID token (ready for output), {@code null} if 077 * not requested. 078 * @param accessToken The UserInfo access token, {@code null} if not 079 * requested. 080 * @param state The state, {@code null} if not requested. 081 */ 082 public AuthenticationSuccessResponse(final URI redirectURI, 083 final AuthorizationCode code, 084 final JWT idToken, 085 final AccessToken accessToken, 086 final State state) { 087 088 super(redirectURI, code, accessToken, state); 089 090 this.idToken = idToken; 091 } 092 093 094 @Override 095 public ResponseType impliedResponseType() { 096 097 ResponseType rt = new ResponseType(); 098 099 if (getAuthorizationCode() != null) { 100 rt.add(ResponseType.Value.CODE); 101 } 102 103 if (getIDToken() != null) { 104 rt.add(OIDCResponseTypeValue.ID_TOKEN); 105 } 106 107 if (getAccessToken() != null) { 108 rt.add(ResponseType.Value.TOKEN); 109 } 110 111 return rt; 112 } 113 114 115 /** 116 * Gets the requested ID token. 117 * 118 * @return The ID token (ready for output), {@code null} if not 119 * requested. 120 */ 121 public JWT getIDToken() { 122 123 return idToken; 124 } 125 126 127 @Override 128 public Map<String,String> toParameters() 129 throws SerializeException { 130 131 Map<String,String> params = super.toParameters(); 132 133 if (idToken != null) { 134 135 try { 136 params.put("id_token", idToken.serialize()); 137 138 } catch (IllegalStateException e) { 139 140 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 141 142 } 143 } 144 145 return params; 146 } 147 148 149 @Override 150 public URI toURI() 151 throws SerializeException { 152 153 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 154 155 // Fragment or query string? 156 if (idToken != null || getAccessToken() != null) { 157 sb.append('#'); 158 } else { 159 sb.append('?'); 160 } 161 162 sb.append(URLUtils.serializeParameters(toParameters())); 163 164 try { 165 return new URI(sb.toString()); 166 167 } catch (URISyntaxException e) { 168 169 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 170 } 171 } 172 173 174 /** 175 * Parses an OpenID Connect authentication success response from the 176 * specified redirection URI and parameters. 177 * 178 * @param redirectURI The base redirection URI. Must not be 179 * {@code null}. 180 * @param params The response parameters to parse. Must not be 181 * {@code null}. 182 * 183 * @return The OpenID Connect authentication success response. 184 * 185 * @throws ParseException If the parameters couldn't be parsed to an 186 * OpenID Connect authentication success 187 * response. 188 */ 189 public static AuthenticationSuccessResponse parse(final URI redirectURI, 190 final Map<String,String> params) 191 throws ParseException { 192 193 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 194 195 // Parse id_token parameter 196 JWT idToken = null; 197 198 if (params.get("id_token") != null) { 199 200 try { 201 idToken = JWTParser.parse(params.get("id_token")); 202 203 } catch (java.text.ParseException e) { 204 205 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 206 } 207 } 208 209 return new AuthenticationSuccessResponse(redirectURI, 210 asr.getAuthorizationCode(), 211 idToken, 212 asr.getAccessToken(), 213 asr.getState()); 214 } 215 216 217 /** 218 * Parses an OpenID Connect authentication success response from the 219 * specified URI. 220 * 221 * <p>Example URI: 222 * 223 * <pre> 224 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 225 * </pre> 226 * 227 * @param uri The URI to parse. Can be absolute or relative, with a 228 * fragment or query string containing the authentication 229 * response parameters. Must not be {@code null}. 230 * 231 * @return The OpenID Connect authentication success response. 232 * 233 * @throws ParseException If the redirection URI couldn't be parsed to 234 * an OpenID Connect authentication success 235 * response. 236 */ 237 public static AuthenticationSuccessResponse parse(final URI uri) 238 throws ParseException { 239 240 String paramString; 241 242 if (uri.getRawQuery() != null) { 243 244 paramString = uri.getRawQuery(); 245 246 } else if (uri.getRawFragment() != null) { 247 248 paramString = uri.getRawFragment(); 249 250 } else { 251 throw new ParseException("Missing authorization response parameters"); 252 } 253 254 Map<String,String> params = URLUtils.parseParameters(paramString); 255 256 if (params == null) { 257 throw new ParseException("Missing or invalid authorization response parameters"); 258 } 259 260 return parse(URIUtils.getBaseURI(uri), params); 261 } 262 263 264 /** 265 * Parses an OpenID Connect authentication success response from the 266 * specified HTTP response. 267 * 268 * <p>Example HTTP response: 269 * 270 * <pre> 271 * HTTP/1.1 302 Found 272 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 273 * </pre> 274 * 275 * @param httpResponse The HTTP response to parse. Must not be 276 * {@code null}. 277 * 278 * @return The OpenID Connect authentication success response. 279 * 280 * @throws ParseException If the HTTP response couldn't be parsed to an 281 * OpenID Connect authentication success 282 * response. 283 */ 284 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 285 throws ParseException { 286 287 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 288 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 289 httpResponse.getStatusCode()); 290 291 URL location = httpResponse.getLocation(); 292 293 if (location == null) 294 throw new ParseException("Missing redirection URI / HTTP Location header"); 295 296 try { 297 return parse(location.toURI()); 298 299 } catch (URISyntaxException e) { 300 301 throw new ParseException(e.getMessage(), e); 302 } 303 } 304}