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 Identification (RFC 9207) 076 * </ul> 077 */ 078@Immutable 079public class AuthenticationSuccessResponse 080 extends AuthorizationSuccessResponse 081 implements AuthenticationResponse { 082 083 084 /** 085 * The ID token, if requested. 086 */ 087 private final JWT idToken; 088 089 090 /** 091 * The session state, required if session management is supported. 092 */ 093 private final State sessionState; 094 095 096 /** 097 * Creates a new OpenID Connect authentication success response. 098 * 099 * @param redirectURI The requested redirection URI. Must not be 100 * {@code null}. 101 * @param code The authorisation code, {@code null} if not 102 * requested. 103 * @param idToken The ID token (ready for output), {@code null} if 104 * not requested. 105 * @param accessToken The UserInfo access token, {@code null} if not 106 * requested. 107 * @param state The state, {@code null} if not requested. 108 * @param sessionState The session state, {@code null} if session 109 * management is not supported. 110 * @param rm The response mode, {@code null} if not 111 * specified. 112 */ 113 public AuthenticationSuccessResponse(final URI redirectURI, 114 final AuthorizationCode code, 115 final JWT idToken, 116 final AccessToken accessToken, 117 final State state, 118 final State sessionState, 119 final ResponseMode rm) { 120 121 this(redirectURI, code, idToken, accessToken, state, sessionState, null, rm); 122 } 123 124 125 /** 126 * Creates a new OpenID Connect authentication success response. 127 * 128 * @param redirectURI The requested redirection URI. Must not be 129 * {@code null}. 130 * @param code The authorisation code, {@code null} if not 131 * requested. 132 * @param idToken The ID token (ready for output), {@code null} if 133 * not requested. 134 * @param accessToken The UserInfo access token, {@code null} if not 135 * requested. 136 * @param state The state, {@code null} if not requested. 137 * @param sessionState The session state, {@code null} if session 138 * management is not supported. 139 * @param rm The response mode, {@code null} if not 140 * specified. 141 */ 142 public AuthenticationSuccessResponse(final URI redirectURI, 143 final AuthorizationCode code, 144 final JWT idToken, 145 final AccessToken accessToken, 146 final State state, 147 final State sessionState, 148 final Issuer issuer, 149 final ResponseMode rm) { 150 151 super(redirectURI, code, accessToken, state, issuer, rm); 152 153 this.idToken = idToken; 154 155 this.sessionState = sessionState; 156 } 157 158 159 /** 160 * Creates a new JSON Web Token (JWT) secured OpenID Connect 161 * authentication success response. 162 * 163 * @param redirectURI The requested redirection URI. Must not be 164 * {@code null}. 165 * @param jwtResponse The JWT-secured response. Must not be 166 * {@code null}. 167 * @param rm The response mode, {@code null} if not specified. 168 */ 169 public AuthenticationSuccessResponse(final URI redirectURI, 170 final JWT jwtResponse, 171 final ResponseMode rm) { 172 173 super(redirectURI, jwtResponse, rm); 174 idToken = null; 175 sessionState = null; 176 } 177 178 179 @Override 180 public ResponseType impliedResponseType() { 181 182 ResponseType rt = new ResponseType(); 183 184 if (getAuthorizationCode() != null) { 185 rt.add(ResponseType.Value.CODE); 186 } 187 188 if (getIDToken() != null) { 189 rt.add(OIDCResponseTypeValue.ID_TOKEN); 190 } 191 192 if (getAccessToken() != null) { 193 rt.add(ResponseType.Value.TOKEN); 194 } 195 196 return rt; 197 } 198 199 200 @Override 201 public ResponseMode impliedResponseMode() { 202 203 if (getResponseMode() != null) { 204 return getResponseMode(); 205 } else { 206 if (getJWTResponse() != null) { 207 // JARM 208 return ResponseMode.JWT; 209 } else if (getAccessToken() != null || getIDToken() != null) { 210 return ResponseMode.FRAGMENT; 211 } else { 212 return ResponseMode.QUERY; 213 } 214 } 215 } 216 217 218 /** 219 * Gets the requested ID token. 220 * 221 * @return The ID token (ready for output), {@code null} if not 222 * requested. 223 */ 224 public JWT getIDToken() { 225 226 return idToken; 227 } 228 229 230 /** 231 * Gets the session state for session management. 232 * 233 * @return The session store, {@code null} if session management is not 234 * supported. 235 */ 236 public State getSessionState() { 237 238 return sessionState; 239 } 240 241 242 @Override 243 public Map<String,List<String>> toParameters() { 244 245 Map<String,List<String>> params = super.toParameters(); 246 247 if (getJWTResponse() != null) { 248 // JARM, no other top-level parameters 249 return params; 250 } 251 252 if (idToken != null) { 253 254 try { 255 params.put("id_token", Collections.singletonList(idToken.serialize())); 256 257 } catch (IllegalStateException e) { 258 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 259 } 260 } 261 262 if (sessionState != null) { 263 264 params.put("session_state", Collections.singletonList(sessionState.getValue())); 265 } 266 267 return params; 268 } 269 270 271 @Override 272 public AuthenticationSuccessResponse toSuccessResponse() { 273 return this; 274 } 275 276 277 @Override 278 public AuthenticationErrorResponse toErrorResponse() { 279 throw new ClassCastException("Cannot cast to AuthenticationErrorResponse"); 280 } 281 282 283 /** 284 * Parses an OpenID Connect authentication success response. 285 * 286 * @param redirectURI The base redirection URI. Must not be 287 * {@code null}. 288 * @param params The response parameters to parse. Must not be 289 * {@code null}. 290 * 291 * @return The OpenID Connect authentication success response. 292 * 293 * @throws ParseException If the parameters couldn't be parsed to an 294 * OpenID Connect authentication success 295 * response. 296 */ 297 public static AuthenticationSuccessResponse parse(final URI redirectURI, 298 final Map<String,List<String>> params) 299 throws ParseException { 300 301 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 302 303 // JARM, ignore other top level params 304 if (asr.getJWTResponse() != null) { 305 return new AuthenticationSuccessResponse(redirectURI, asr.getJWTResponse(), asr.getResponseMode()); 306 } 307 308 // Parse id_token parameter 309 String idTokenString = MultivaluedMapUtils.getFirstValue(params, "id_token"); 310 JWT idToken = null; 311 if (idTokenString != null) { 312 313 try { 314 idToken = JWTParser.parse(idTokenString); 315 316 } catch (java.text.ParseException e) { 317 318 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 319 } 320 } 321 322 // Parse the optional session_state parameter 323 324 State sessionState = null; 325 326 if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "session_state"))) { 327 328 sessionState = new State(MultivaluedMapUtils.getFirstValue(params, "session_state")); 329 } 330 331 return new AuthenticationSuccessResponse(redirectURI, 332 asr.getAuthorizationCode(), 333 idToken, 334 asr.getAccessToken(), 335 asr.getState(), 336 sessionState, 337 asr.getIssuer(), 338 null); 339 } 340 341 342 /** 343 * Parses an OpenID Connect authentication success response. 344 * 345 * <p>Use a relative URI if the host, port and path details are not 346 * known: 347 * 348 * <pre> 349 * URI relUrl = new URI("https:///?code=Qcb0Orv1...&state=af0ifjsldkj"); 350 * </pre> 351 * 352 * <p>Example URI: 353 * 354 * <pre> 355 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 356 * </pre> 357 * 358 * @param uri The URI to parse. Can be absolute or relative, with a 359 * fragment or query string containing the authentication 360 * response parameters. Must not be {@code null}. 361 * 362 * @return The OpenID Connect authentication success response. 363 * 364 * @throws ParseException If the redirection URI couldn't be parsed to 365 * an OpenID Connect authentication success 366 * response. 367 */ 368 public static AuthenticationSuccessResponse parse(final URI uri) 369 throws ParseException { 370 371 return parse(URIUtils.getBaseURI(uri), parseResponseParameters(uri)); 372 } 373 374 375 /** 376 * Parses an OpenID Connect authentication success response from the 377 * specified initial HTTP 302 redirect response generated at the 378 * authorisation endpoint. 379 * 380 * <p>Example HTTP response: 381 * 382 * <pre> 383 * HTTP/1.1 302 Found 384 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 385 * </pre> 386 * 387 * @see #parse(HTTPRequest) 388 * 389 * @param httpResponse The HTTP response to parse. Must not be 390 * {@code null}. 391 * 392 * @return The OpenID Connect authentication success response. 393 * 394 * @throws ParseException If the HTTP response couldn't be parsed to an 395 * OpenID Connect authentication success 396 * response. 397 */ 398 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 399 throws ParseException { 400 401 URI location = httpResponse.getLocation(); 402 403 if (location == null) 404 throw new ParseException("Missing redirection URI / HTTP Location header"); 405 406 return parse(location); 407 } 408 409 410 /** 411 * Parses an OpenID Connect authentication success response from the 412 * specified HTTP request at the client redirection (callback) URI. 413 * Applies to {@code query}, {@code fragment} and {@code form_post} 414 * response modes. 415 * 416 * <p>Example HTTP request (authentication success): 417 * 418 * <pre> 419 * GET /cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz HTTP/1.1 420 * Host: client.example.com 421 * </pre> 422 * 423 * @see #parse(HTTPResponse) 424 * 425 * @param httpRequest The HTTP request to parse. Must not be 426 * {@code null}. 427 * 428 * @return The authentication success response. 429 * 430 * @throws ParseException If the HTTP request couldn't be parsed to an 431 * OpenID Connect authentication success 432 * response. 433 */ 434 public static AuthenticationSuccessResponse parse(final HTTPRequest httpRequest) 435 throws ParseException { 436 437 return parse(httpRequest.getURI(), parseResponseParameters(httpRequest)); 438 } 439}