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