001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk; 019 020 021import java.net.URI; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025 026import com.nimbusds.jwt.JWT; 027import com.nimbusds.jwt.JWTParser; 028import com.nimbusds.oauth2.sdk.*; 029import com.nimbusds.oauth2.sdk.http.HTTPRequest; 030import com.nimbusds.oauth2.sdk.http.HTTPResponse; 031import com.nimbusds.oauth2.sdk.id.Issuer; 032import com.nimbusds.oauth2.sdk.id.State; 033import com.nimbusds.oauth2.sdk.token.AccessToken; 034import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 035import com.nimbusds.oauth2.sdk.util.StringUtils; 036import com.nimbusds.oauth2.sdk.util.URIUtils; 037import net.jcip.annotations.Immutable; 038 039 040/** 041 * OpenID Connect authentication success response. Used to return an 042 * authorisation code, access token and / or ID Token at the Authorisation 043 * endpoint. 044 * 045 * <p>Example HTTP response with code and ID Token (code flow): 046 * 047 * <pre> 048 * HTTP/1.1 302 Found 049 * Location: https://client.example.org/cb# 050 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 051 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 052 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 053 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 054 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 055 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 056 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 057 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 058 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 059 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 060 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 061 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 062 * </pre> 063 * 064 * <p>Related specifications: 065 * 066 * <ul> 067 * <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6, 068 * 3.3.2.5 and 3.3.2.6. 069 * <li>OpenID Connect Session Management 1.0 - draft 23, section 3. 070 * <li>OAuth 2.0 (RFC 6749), section 3.1. 071 * <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0. 072 * <li>OAuth 2.0 Form Post Response Mode 1.0. 073 * <li>Financial-grade API: JWT Secured Authorization Response Mode for 074 * OAuth 2.0 (JARM). 075 * <li>OAuth 2.0 Authorization Server Issuer Identifier in Authorization 076 * Response (draft-ietf-oauth-iss-auth-resp-00). 077 * </ul> 078 */ 079@Immutable 080public class AuthenticationSuccessResponse 081 extends AuthorizationSuccessResponse 082 implements AuthenticationResponse { 083 084 085 /** 086 * The ID token, if requested. 087 */ 088 private final JWT idToken; 089 090 091 /** 092 * The session state, required if session management is supported. 093 */ 094 private final State sessionState; 095 096 097 /** 098 * Creates a new OpenID Connect authentication success response. 099 * 100 * @param redirectURI The requested redirection URI. Must not be 101 * {@code null}. 102 * @param code The authorisation code, {@code null} if not 103 * requested. 104 * @param idToken The ID token (ready for output), {@code null} if 105 * not requested. 106 * @param accessToken The UserInfo access token, {@code null} if not 107 * requested. 108 * @param state The state, {@code null} if not requested. 109 * @param sessionState The session state, {@code null} if session 110 * management is not supported. 111 * @param rm The response mode, {@code null} if not 112 * specified. 113 */ 114 public AuthenticationSuccessResponse(final URI redirectURI, 115 final AuthorizationCode code, 116 final JWT idToken, 117 final AccessToken accessToken, 118 final State state, 119 final State sessionState, 120 final ResponseMode rm) { 121 122 this(redirectURI, code, idToken, accessToken, state, sessionState, null, rm); 123 } 124 125 126 /** 127 * Creates a new OpenID Connect authentication success response. 128 * 129 * @param redirectURI The requested redirection URI. Must not be 130 * {@code null}. 131 * @param code The authorisation code, {@code null} if not 132 * requested. 133 * @param idToken The ID token (ready for output), {@code null} if 134 * not requested. 135 * @param accessToken The UserInfo access token, {@code null} if not 136 * requested. 137 * @param state The state, {@code null} if not requested. 138 * @param sessionState The session state, {@code null} if session 139 * management is not supported. 140 * @param rm The response mode, {@code null} if not 141 * specified. 142 */ 143 public AuthenticationSuccessResponse(final URI redirectURI, 144 final AuthorizationCode code, 145 final JWT idToken, 146 final AccessToken accessToken, 147 final State state, 148 final State sessionState, 149 final Issuer issuer, 150 final ResponseMode rm) { 151 152 super(redirectURI, code, accessToken, state, issuer, rm); 153 154 this.idToken = idToken; 155 156 this.sessionState = sessionState; 157 } 158 159 160 /** 161 * Creates a new JSON Web Token (JWT) secured OpenID Connect 162 * authentication success response. 163 * 164 * @param redirectURI The requested redirection URI. Must not be 165 * {@code null}. 166 * @param jwtResponse The JWT-secured response. Must not be 167 * {@code null}. 168 * @param rm The response mode, {@code null} if not specified. 169 */ 170 public AuthenticationSuccessResponse(final URI redirectURI, 171 final JWT jwtResponse, 172 final ResponseMode rm) { 173 174 super(redirectURI, jwtResponse, rm); 175 idToken = null; 176 sessionState = null; 177 } 178 179 180 @Override 181 public ResponseType impliedResponseType() { 182 183 ResponseType rt = new ResponseType(); 184 185 if (getAuthorizationCode() != null) { 186 rt.add(ResponseType.Value.CODE); 187 } 188 189 if (getIDToken() != null) { 190 rt.add(OIDCResponseTypeValue.ID_TOKEN); 191 } 192 193 if (getAccessToken() != null) { 194 rt.add(ResponseType.Value.TOKEN); 195 } 196 197 return rt; 198 } 199 200 201 @Override 202 public ResponseMode impliedResponseMode() { 203 204 if (getResponseMode() != null) { 205 return getResponseMode(); 206 } else { 207 if (getJWTResponse() != null) { 208 // JARM 209 return ResponseMode.JWT; 210 } else if (getAccessToken() != null || getIDToken() != null) { 211 return ResponseMode.FRAGMENT; 212 } else { 213 return ResponseMode.QUERY; 214 } 215 } 216 } 217 218 219 /** 220 * Gets the requested ID token. 221 * 222 * @return The ID token (ready for output), {@code null} if not 223 * requested. 224 */ 225 public JWT getIDToken() { 226 227 return idToken; 228 } 229 230 231 /** 232 * Gets the session state for session management. 233 * 234 * @return The session store, {@code null} if session management is not 235 * supported. 236 */ 237 public State getSessionState() { 238 239 return sessionState; 240 } 241 242 243 @Override 244 public Map<String,List<String>> toParameters() { 245 246 Map<String,List<String>> params = super.toParameters(); 247 248 if (getJWTResponse() != null) { 249 // JARM, no other top-level parameters 250 return params; 251 } 252 253 if (idToken != null) { 254 255 try { 256 params.put("id_token", Collections.singletonList(idToken.serialize())); 257 258 } catch (IllegalStateException e) { 259 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 260 } 261 } 262 263 if (sessionState != null) { 264 265 params.put("session_state", Collections.singletonList(sessionState.getValue())); 266 } 267 268 return params; 269 } 270 271 272 @Override 273 public AuthenticationSuccessResponse toSuccessResponse() { 274 return this; 275 } 276 277 278 @Override 279 public AuthenticationErrorResponse toErrorResponse() { 280 throw new ClassCastException("Cannot cast to AuthenticationErrorResponse"); 281 } 282 283 284 /** 285 * Parses an OpenID Connect authentication success response. 286 * 287 * @param redirectURI The base redirection URI. Must not be 288 * {@code null}. 289 * @param params The response parameters to parse. Must not be 290 * {@code null}. 291 * 292 * @return The OpenID Connect authentication success response. 293 * 294 * @throws ParseException If the parameters couldn't be parsed to an 295 * OpenID Connect authentication success 296 * response. 297 */ 298 public static AuthenticationSuccessResponse parse(final URI redirectURI, 299 final Map<String,List<String>> params) 300 throws ParseException { 301 302 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 303 304 // JARM, ignore other top level params 305 if (asr.getJWTResponse() != null) { 306 return new AuthenticationSuccessResponse(redirectURI, asr.getJWTResponse(), asr.getResponseMode()); 307 } 308 309 // Parse id_token parameter 310 String idTokenString = MultivaluedMapUtils.getFirstValue(params, "id_token"); 311 JWT idToken = null; 312 if (idTokenString != null) { 313 314 try { 315 idToken = JWTParser.parse(idTokenString); 316 317 } catch (java.text.ParseException e) { 318 319 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 320 } 321 } 322 323 // Parse the optional session_state parameter 324 325 State sessionState = null; 326 327 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "session_state"))) { 328 329 sessionState = new State(MultivaluedMapUtils.getFirstValue(params, "session_state")); 330 } 331 332 return new AuthenticationSuccessResponse(redirectURI, 333 asr.getAuthorizationCode(), 334 idToken, 335 asr.getAccessToken(), 336 asr.getState(), 337 sessionState, 338 asr.getIssuer(), 339 null); 340 } 341 342 343 /** 344 * Parses an OpenID Connect authentication success response. 345 * 346 * <p>Use a relative URI if the host, port and path details are not 347 * known: 348 * 349 * <pre> 350 * URI relUrl = new URI("https:///?code=Qcb0Orv1...&state=af0ifjsldkj"); 351 * </pre> 352 * 353 * <p>Example URI: 354 * 355 * <pre> 356 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 357 * </pre> 358 * 359 * @param uri The URI to parse. Can be absolute or relative, with a 360 * fragment or query string containing the authentication 361 * response parameters. Must not be {@code null}. 362 * 363 * @return The OpenID Connect authentication success response. 364 * 365 * @throws ParseException If the redirection URI couldn't be parsed to 366 * an OpenID Connect authentication success 367 * response. 368 */ 369 public static AuthenticationSuccessResponse parse(final URI uri) 370 throws ParseException { 371 372 return parse(URIUtils.getBaseURI(uri), parseResponseParameters(uri)); 373 } 374 375 376 /** 377 * Parses an OpenID Connect authentication success response from the 378 * specified initial HTTP 302 redirect response generated at the 379 * authorisation endpoint. 380 * 381 * <p>Example HTTP response: 382 * 383 * <pre> 384 * HTTP/1.1 302 Found 385 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 386 * </pre> 387 * 388 * @see #parse(HTTPRequest) 389 * 390 * @param httpResponse The HTTP response to parse. Must not be 391 * {@code null}. 392 * 393 * @return The OpenID Connect authentication success response. 394 * 395 * @throws ParseException If the HTTP response couldn't be parsed to an 396 * OpenID Connect authentication success 397 * response. 398 */ 399 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 400 throws ParseException { 401 402 URI location = httpResponse.getLocation(); 403 404 if (location == null) 405 throw new ParseException("Missing redirection URI / HTTP Location header"); 406 407 return parse(location); 408 } 409 410 411 /** 412 * Parses an OpenID Connect authentication success response from the 413 * specified HTTP request at the client redirection (callback) URI. 414 * Applies to {@code query}, {@code fragment} and {@code form_post} 415 * response modes. 416 * 417 * <p>Example HTTP request (authentication success): 418 * 419 * <pre> 420 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 421 * Host: client.example.com 422 * </pre> 423 * 424 * @see #parse(HTTPResponse) 425 * 426 * @param httpRequest The HTTP request to parse. Must not be 427 * {@code null}. 428 * 429 * @return The authentication success response. 430 * 431 * @throws ParseException If the HTTP request couldn't be parsed to an 432 * OpenID Connect authentication success 433 * response. 434 */ 435 public static AuthenticationSuccessResponse parse(final HTTPRequest httpRequest) 436 throws ParseException { 437 438 return parse(httpRequest.getURI(), parseResponseParameters(httpRequest)); 439 } 440}