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.net.URISyntaxException; 023import java.util.*; 024 025import com.nimbusds.jwt.JWT; 026import com.nimbusds.jwt.JWTParser; 027import com.nimbusds.langtag.LangTag; 028import com.nimbusds.langtag.LangTagException; 029import com.nimbusds.oauth2.sdk.*; 030import com.nimbusds.oauth2.sdk.http.HTTPRequest; 031import com.nimbusds.oauth2.sdk.id.ClientID; 032import com.nimbusds.oauth2.sdk.id.State; 033import com.nimbusds.oauth2.sdk.pkce.CodeChallenge; 034import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod; 035import com.nimbusds.oauth2.sdk.pkce.CodeVerifier; 036import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 037import com.nimbusds.oauth2.sdk.util.URIUtils; 038import com.nimbusds.oauth2.sdk.util.URLUtils; 039import com.nimbusds.openid.connect.sdk.claims.ACR; 040import net.jcip.annotations.Immutable; 041import net.minidev.json.JSONObject; 042import org.apache.commons.lang3.StringUtils; 043 044 045/** 046 * OpenID Connect authentication request. Intended to authenticate an end-user 047 * and request the end-user's authorisation to release information to the 048 * client. Supports custom request parameters. 049 * 050 * <p>Example HTTP request (code flow): 051 * 052 * <pre> 053 * https://server.example.com/op/authorize? 054 * response_type=code%20id_token 055 * &client_id=s6BhdRkqt3 056 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb 057 * &scope=openid 058 * &nonce=n-0S6_WzA2Mj 059 * &state=af0ifjsldkj 060 * </pre> 061 * 062 * <p>Related specifications: 063 * 064 * <ul> 065 * <li>OpenID Connect Core 1.0, section 3.1.2.1. 066 * <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636). 067 * </ul> 068 */ 069@Immutable 070public class AuthenticationRequest extends AuthorizationRequest { 071 072 073 /** 074 * The registered parameter names. 075 */ 076 private static final Set<String> REGISTERED_PARAMETER_NAMES; 077 078 079 /** 080 * Initialises the registered parameter name set. 081 */ 082 static { 083 Set<String> p = new HashSet<>(); 084 085 p.addAll(AuthorizationRequest.getRegisteredParameterNames()); 086 087 p.add("nonce"); 088 p.add("display"); 089 p.add("prompt"); 090 p.add("max_age"); 091 p.add("ui_locales"); 092 p.add("claims_locales"); 093 p.add("id_token_hint"); 094 p.add("login_hint"); 095 p.add("acr_values"); 096 p.add("claims"); 097 p.add("request_uri"); 098 p.add("request"); 099 100 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 101 } 102 103 104 /** 105 * The nonce (required for implicit flow, optional for code flow). 106 */ 107 private final Nonce nonce; 108 109 110 /** 111 * The requested display type (optional). 112 */ 113 private final Display display; 114 115 116 /** 117 * The requested prompt (optional). 118 */ 119 private final Prompt prompt; 120 121 122 /** 123 * The required maximum authentication age, in seconds, -1 if not 124 * specified, zero implies prompt=login (optional). 125 */ 126 private final int maxAge; 127 128 129 /** 130 * The end-user's preferred languages and scripts for the user 131 * interface (optional). 132 */ 133 private final List<LangTag> uiLocales; 134 135 136 /** 137 * The end-user's preferred languages and scripts for claims being 138 * returned (optional). 139 */ 140 private final List<LangTag> claimsLocales; 141 142 143 /** 144 * Previously issued ID Token passed to the authorisation server as a 145 * hint about the end-user's current or past authenticated session with 146 * the client (optional). Should be present when {@code prompt=none} is 147 * used. 148 */ 149 private final JWT idTokenHint; 150 151 152 /** 153 * Hint to the authorisation server about the login identifier the 154 * end-user may use to log in (optional). 155 */ 156 private final String loginHint; 157 158 159 /** 160 * Requested Authentication Context Class Reference values (optional). 161 */ 162 private final List<ACR> acrValues; 163 164 165 /** 166 * Individual claims to be returned (optional). 167 */ 168 private final ClaimsRequest claims; 169 170 171 /** 172 * Request object (optional). 173 */ 174 private final JWT requestObject; 175 176 177 /** 178 * Request object URI (optional). 179 */ 180 private final URI requestURI; 181 182 183 /** 184 * Builder for constructing OpenID Connect authentication requests. 185 */ 186 public static class Builder { 187 188 189 /** 190 * The endpoint URI (optional). 191 */ 192 private URI uri; 193 194 195 /** 196 * The response type (required). 197 */ 198 private final ResponseType rt; 199 200 201 /** 202 * The client identifier (required). 203 */ 204 private final ClientID clientID; 205 206 207 /** 208 * The redirection URI where the response will be sent 209 * (required). 210 */ 211 private final URI redirectURI; 212 213 214 /** 215 * The scope (required). 216 */ 217 private final Scope scope; 218 219 220 /** 221 * The opaque value to maintain state between the request and 222 * the callback (recommended). 223 */ 224 private State state; 225 226 227 /** 228 * The nonce (required for implicit flow, optional for code 229 * flow). 230 */ 231 private Nonce nonce; 232 233 234 /** 235 * The requested display type (optional). 236 */ 237 private Display display; 238 239 240 /** 241 * The requested prompt (optional). 242 */ 243 private Prompt prompt; 244 245 246 /** 247 * The required maximum authentication age, in seconds, -1 if 248 * not specified, zero implies prompt=login (optional). 249 */ 250 private int maxAge = -1; 251 252 253 /** 254 * The end-user's preferred languages and scripts for the user 255 * interface (optional). 256 */ 257 private List<LangTag> uiLocales; 258 259 260 /** 261 * The end-user's preferred languages and scripts for claims 262 * being returned (optional). 263 */ 264 private List<LangTag> claimsLocales; 265 266 267 /** 268 * Previously issued ID Token passed to the authorisation 269 * server as a hint about the end-user's current or past 270 * authenticated session with the client (optional). Should be 271 * present when {@code prompt=none} is used. 272 */ 273 private JWT idTokenHint; 274 275 276 /** 277 * Hint to the authorisation server about the login identifier 278 * the end-user may use to log in (optional). 279 */ 280 private String loginHint; 281 282 283 /** 284 * Requested Authentication Context Class Reference values 285 * (optional). 286 */ 287 private List<ACR> acrValues; 288 289 290 /** 291 * Individual claims to be returned (optional). 292 */ 293 private ClaimsRequest claims; 294 295 296 /** 297 * Request object (optional). 298 */ 299 private JWT requestObject; 300 301 302 /** 303 * Request object URI (optional). 304 */ 305 private URI requestURI; 306 307 308 /** 309 * The response mode (optional). 310 */ 311 private ResponseMode rm; 312 313 314 /** 315 * The authorisation code challenge for PKCE (optional). 316 */ 317 private CodeChallenge codeChallenge; 318 319 320 /** 321 * The authorisation code challenge method for PKCE (optional). 322 */ 323 private CodeChallengeMethod codeChallengeMethod; 324 325 326 /** 327 * The additional custom parameters. 328 */ 329 private Map<String,String> customParams = new HashMap<>(); 330 331 332 /** 333 * Creates a new OpenID Connect authentication request builder. 334 * 335 * @param rt The response type. Corresponds to the 336 * {@code response_type} parameter. Must 337 * specify a valid OpenID Connect response 338 * type. Must not be {@code null}. 339 * @param scope The request scope. Corresponds to the 340 * {@code scope} parameter. Must contain an 341 * {@link OIDCScopeValue#OPENID openid 342 * value}. Must not be {@code null}. 343 * @param clientID The client identifier. Corresponds to the 344 * {@code client_id} parameter. Must not be 345 * {@code null}. 346 * @param redirectURI The redirection URI. Corresponds to the 347 * {@code redirect_uri} parameter. Must not 348 * be {@code null} unless set by means of 349 * the optional {@code request_object} / 350 * {@code request_uri} parameter. 351 */ 352 public Builder(final ResponseType rt, 353 final Scope scope, 354 final ClientID clientID, 355 final URI redirectURI) { 356 357 if (rt == null) 358 throw new IllegalArgumentException("The response type must not be null"); 359 360 OIDCResponseTypeValidator.validate(rt); 361 362 this.rt = rt; 363 364 if (scope == null) 365 throw new IllegalArgumentException("The scope must not be null"); 366 367 if (! scope.contains(OIDCScopeValue.OPENID)) 368 throw new IllegalArgumentException("The scope must include an \"openid\" value"); 369 370 this.scope = scope; 371 372 if (clientID == null) 373 throw new IllegalArgumentException("The client ID must not be null"); 374 375 this.clientID = clientID; 376 377 // Check presence at build time 378 this.redirectURI = redirectURI; 379 } 380 381 382 /** 383 * Creates a new OpenID Connect authentication request builder 384 * from the specified request. 385 * 386 * @param request The OpenID Connect authentication request. 387 * Must not be {@code null}. 388 */ 389 public Builder(final AuthenticationRequest request) { 390 391 uri = request.getEndpointURI(); 392 rt = request.getResponseType(); 393 clientID = request.getClientID(); 394 redirectURI = request.getRedirectionURI(); 395 scope = request.getScope(); 396 state = request.getState(); 397 nonce = request.getNonce(); 398 display = request.getDisplay(); 399 prompt = request.getPrompt(); 400 maxAge = request.getMaxAge(); 401 uiLocales = request.getUILocales(); 402 claimsLocales = request.getClaimsLocales(); 403 idTokenHint = request.getIDTokenHint(); 404 loginHint = request.getLoginHint(); 405 acrValues = request.getACRValues(); 406 claims = request.getClaims(); 407 requestObject = request.getRequestObject(); 408 requestURI = request.getRequestURI(); 409 rm = request.getResponseMode(); 410 codeChallenge = request.getCodeChallenge(); 411 codeChallengeMethod = request.getCodeChallengeMethod(); 412 customParams.putAll(request.getCustomParameters()); 413 } 414 415 416 /** 417 * Sets the state. Corresponds to the recommended {@code state} 418 * parameter. 419 * 420 * @param state The state, {@code null} if not specified. 421 * 422 * @return This builder. 423 */ 424 public Builder state(final State state) { 425 426 this.state = state; 427 return this; 428 } 429 430 431 /** 432 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 433 * request is intended. 434 * 435 * @param uri The endpoint URI, {@code null} if not specified. 436 * 437 * @return This builder. 438 */ 439 public Builder endpointURI(final URI uri) { 440 441 this.uri = uri; 442 return this; 443 } 444 445 446 /** 447 * Sets the nonce. Corresponds to the conditionally optional 448 * {@code nonce} parameter. 449 * 450 * @param nonce The nonce, {@code null} if not specified. 451 */ 452 public Builder nonce(final Nonce nonce) { 453 454 this.nonce = nonce; 455 return this; 456 } 457 458 459 /** 460 * Sets the requested display type. Corresponds to the optional 461 * {@code display} parameter. 462 * 463 * @param display The requested display type, {@code null} if 464 * not specified. 465 */ 466 public Builder display(final Display display) { 467 468 this.display = display; 469 return this; 470 } 471 472 473 /** 474 * Sets the requested prompt. Corresponds to the optional 475 * {@code prompt} parameter. 476 * 477 * @param prompt The requested prompt, {@code null} if not 478 * specified. 479 */ 480 public Builder prompt(final Prompt prompt) { 481 482 this.prompt = prompt; 483 return this; 484 } 485 486 487 /** 488 * Sets the required maximum authentication age. Corresponds to 489 * the optional {@code max_age} parameter. 490 * 491 * @param maxAge The maximum authentication age, in seconds; 0 492 * if not specified. 493 */ 494 public Builder maxAge(final int maxAge) { 495 496 this.maxAge = maxAge; 497 return this; 498 } 499 500 501 /** 502 * Sets the end-user's preferred languages and scripts for the 503 * user interface, ordered by preference. Corresponds to the 504 * optional {@code ui_locales} parameter. 505 * 506 * @param uiLocales The preferred UI locales, {@code null} if 507 * not specified. 508 */ 509 public Builder uiLocales(final List<LangTag> uiLocales) { 510 511 this.uiLocales = uiLocales; 512 return this; 513 } 514 515 516 /** 517 * Sets the end-user's preferred languages and scripts for the 518 * claims being returned, ordered by preference. Corresponds to 519 * the optional {@code claims_locales} parameter. 520 * 521 * @param claimsLocales The preferred claims locales, 522 * {@code null} if not specified. 523 */ 524 public Builder claimsLocales(final List<LangTag> claimsLocales) { 525 526 this.claimsLocales = claimsLocales; 527 return this; 528 } 529 530 531 /** 532 * Sets the ID Token hint. Corresponds to the conditionally 533 * optional {@code id_token_hint} parameter. 534 * 535 * @param idTokenHint The ID Token hint, {@code null} if not 536 * specified. 537 */ 538 public Builder idTokenHint(final JWT idTokenHint) { 539 540 this.idTokenHint = idTokenHint; 541 return this; 542 } 543 544 545 /** 546 * Sets the login hint. Corresponds to the optional 547 * {@code login_hint} parameter. 548 * 549 * @param loginHint The login hint, {@code null} if not 550 * specified. 551 */ 552 public Builder loginHint(final String loginHint) { 553 554 this.loginHint = loginHint; 555 return this; 556 } 557 558 559 /** 560 * Sets the requested Authentication Context Class Reference 561 * values. Corresponds to the optional {@code acr_values} 562 * parameter. 563 * 564 * @param acrValues The requested ACR values, {@code null} if 565 * not specified. 566 */ 567 public Builder acrValues(final List<ACR> acrValues) { 568 569 this.acrValues = acrValues; 570 return this; 571 } 572 573 574 /** 575 * Sets the individual claims to be returned. Corresponds to 576 * the optional {@code claims} parameter. 577 * 578 * @param claims The individual claims to be returned, 579 * {@code null} if not specified. 580 */ 581 public Builder claims(final ClaimsRequest claims) { 582 583 this.claims = claims; 584 return this; 585 } 586 587 588 /** 589 * Sets the request object. Corresponds to the optional 590 * {@code request} parameter. Must not be specified together 591 * with a request object URI. 592 * 593 * @return The request object, {@code null} if not specified. 594 */ 595 public Builder requestObject(final JWT requestObject) { 596 597 this.requestObject = requestObject; 598 return this; 599 } 600 601 602 /** 603 * Sets the request object URI. Corresponds to the optional 604 * {@code request_uri} parameter. Must not be specified 605 * together with a request object. 606 * 607 * @param requestURI The request object URI, {@code null} if 608 * not specified. 609 */ 610 public Builder requestURI(final URI requestURI) { 611 612 this.requestURI = requestURI; 613 return this; 614 } 615 616 617 /** 618 * Sets the response mode. Corresponds to the optional 619 * {@code response_mode} parameter. Use of this parameter is 620 * not recommended unless a non-default response mode is 621 * requested (e.g. form_post). 622 * 623 * @param rm The response mode, {@code null} if not specified. 624 * 625 * @return This builder. 626 */ 627 public Builder responseMode(final ResponseMode rm) { 628 629 this.rm = rm; 630 return this; 631 } 632 633 634 /** 635 * Sets the code challenge for Proof Key for Code Exchange 636 * (PKCE) by public OAuth clients. 637 * 638 * @param codeChallenge The code challenge, {@code null} 639 * if not specified. 640 * @param codeChallengeMethod The code challenge method, 641 * {@code null} if not specified. 642 * 643 * @return This builder. 644 */ 645 @Deprecated 646 public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) { 647 648 this.codeChallenge = codeChallenge; 649 this.codeChallengeMethod = codeChallengeMethod; 650 return this; 651 } 652 653 654 /** 655 * Sets the code challenge for Proof Key for Code Exchange 656 * (PKCE) by public OAuth clients. 657 * 658 * @param codeVerifier The code verifier to use to 659 * compute the code challenge, 660 * {@code null} if PKCE is not 661 * specified. 662 * @param codeChallengeMethod The code challenge method, 663 * {@code null} if not specified. 664 * Defaults to 665 * {@link CodeChallengeMethod#PLAIN} 666 * if a code verifier is specified. 667 * 668 * @return This builder. 669 */ 670 public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) { 671 672 if (codeVerifier != null) { 673 CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault(); 674 this.codeChallenge = CodeChallenge.compute(method, codeVerifier); 675 this.codeChallengeMethod = method; 676 } else { 677 this.codeChallenge = null; 678 this.codeChallengeMethod = null; 679 } 680 return this; 681 } 682 683 684 /** 685 * Sets the specified additional custom parameter. 686 * 687 * @param name The parameter name. Must not be {@code null}. 688 * @param value The parameter value, {@code null} if not 689 * specified. 690 * 691 * @return This builder. 692 */ 693 public Builder customParameter(final String name, final String value) { 694 695 customParams.put(name, value); 696 return this; 697 } 698 699 700 /** 701 * Builds a new authentication request. 702 * 703 * @return The authentication request. 704 */ 705 public AuthenticationRequest build() { 706 707 try { 708 return new AuthenticationRequest( 709 uri, rt, rm, scope, clientID, redirectURI, state, nonce, 710 display, prompt, maxAge, uiLocales, claimsLocales, 711 idTokenHint, loginHint, acrValues, claims, 712 requestObject, requestURI, 713 codeChallenge, codeChallengeMethod, 714 customParams); 715 716 } catch (IllegalArgumentException e) { 717 718 throw new IllegalStateException(e.getMessage(), e); 719 } 720 } 721 } 722 723 724 /** 725 * Creates a new minimal OpenID Connect authentication request. 726 * 727 * @param uri The URI of the OAuth 2.0 authorisation endpoint. 728 * May be {@code null} if the {@link #toHTTPRequest} 729 * method will not be used. 730 * @param rt The response type. Corresponds to the 731 * {@code response_type} parameter. Must specify a 732 * valid OpenID Connect response type. Must not be 733 * {@code null}. 734 * @param scope The request scope. Corresponds to the 735 * {@code scope} parameter. Must contain an 736 * {@link OIDCScopeValue#OPENID openid value}. Must 737 * not be {@code null}. 738 * @param clientID The client identifier. Corresponds to the 739 * {@code client_id} parameter. Must not be 740 * {@code null}. 741 * @param redirectURI The redirection URI. Corresponds to the 742 * {@code redirect_uri} parameter. Must not be 743 * {@code null}. 744 * @param state The state. Corresponds to the {@code state} 745 * parameter. May be {@code null}. 746 * @param nonce The nonce. Corresponds to the {@code nonce} 747 * parameter. May be {@code null} for code flow. 748 */ 749 public AuthenticationRequest(final URI uri, 750 final ResponseType rt, 751 final Scope scope, 752 final ClientID clientID, 753 final URI redirectURI, 754 final State state, 755 final Nonce nonce) { 756 757 // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 758 // idTokenHint, loginHint, acrValues, claims 759 // codeChallenge, codeChallengeMethod 760 this(uri, rt, null, scope, clientID, redirectURI, state, nonce, 761 null, null, -1, null, null, 762 null, null, null, null, null, null, 763 null, null); 764 } 765 766 767 /** 768 * Creates a new OpenID Connect authentication request. 769 * 770 * @param uri The URI of the OAuth 2.0 authorisation 771 * endpoint. May be {@code null} if the 772 * {@link #toHTTPRequest} method will not be 773 * used. 774 * @param rt The response type set. Corresponds to the 775 * {@code response_type} parameter. Must 776 * specify a valid OpenID Connect response 777 * type. Must not be {@code null}. 778 * @param rm The response mode. Corresponds to the 779 * optional {@code response_mode} parameter. 780 * Use of this parameter is not recommended 781 * unless a non-default response mode is 782 * requested (e.g. form_post). 783 * @param scope The request scope. Corresponds to the 784 * {@code scope} parameter. Must contain an 785 * {@link OIDCScopeValue#OPENID openid value}. 786 * Must not be {@code null}. 787 * @param clientID The client identifier. Corresponds to the 788 * {@code client_id} parameter. Must not be 789 * {@code null}. 790 * @param redirectURI The redirection URI. Corresponds to the 791 * {@code redirect_uri} parameter. Must not 792 * be {@code null} unless set by means of 793 * the optional {@code request_object} / 794 * {@code request_uri} parameter. 795 * @param state The state. Corresponds to the recommended 796 * {@code state} parameter. {@code null} if 797 * not specified. 798 * @param nonce The nonce. Corresponds to the 799 * {@code nonce} parameter. May be 800 * {@code null} for code flow. 801 * @param display The requested display type. Corresponds 802 * to the optional {@code display} 803 * parameter. 804 * {@code null} if not specified. 805 * @param prompt The requested prompt. Corresponds to the 806 * optional {@code prompt} parameter. 807 * {@code null} if not specified. 808 * @param maxAge The required maximum authentication age, 809 * in seconds. Corresponds to the optional 810 * {@code max_age} parameter. Zero if not 811 * specified. 812 * @param uiLocales The preferred languages and scripts for 813 * the user interface. Corresponds to the 814 * optional {@code ui_locales} parameter. 815 * {@code null} if not specified. 816 * @param claimsLocales The preferred languages and scripts for 817 * claims being returned. Corresponds to the 818 * optional {@code claims_locales} 819 * parameter. {@code null} if not specified. 820 * @param idTokenHint The ID Token hint. Corresponds to the 821 * optional {@code id_token_hint} parameter. 822 * {@code null} if not specified. 823 * @param loginHint The login hint. Corresponds to the 824 * optional {@code login_hint} parameter. 825 * {@code null} if not specified. 826 * @param acrValues The requested Authentication Context 827 * Class Reference values. Corresponds to 828 * the optional {@code acr_values} 829 * parameter. {@code null} if not specified. 830 * @param claims The individual claims to be returned. 831 * Corresponds to the optional 832 * {@code claims} parameter. {@code null} if 833 * not specified. 834 * @param requestObject The request object. Corresponds to the 835 * optional {@code request} parameter. Must 836 * not be specified together with a request 837 * object URI. {@code null} if not 838 * specified. 839 * @param requestURI The request object URI. Corresponds to 840 * the optional {@code request_uri} 841 * parameter. Must not be specified together 842 * with a request object. {@code null} if 843 * not specified. 844 * @param codeChallenge The code challenge for PKCE, {@code null} 845 * if not specified. 846 * @param codeChallengeMethod The code challenge method for PKCE, 847 * {@code null} if not specified. 848 */ 849 public AuthenticationRequest(final URI uri, 850 final ResponseType rt, 851 final ResponseMode rm, 852 final Scope scope, 853 final ClientID clientID, 854 final URI redirectURI, 855 final State state, 856 final Nonce nonce, 857 final Display display, 858 final Prompt prompt, 859 final int maxAge, 860 final List<LangTag> uiLocales, 861 final List<LangTag> claimsLocales, 862 final JWT idTokenHint, 863 final String loginHint, 864 final List<ACR> acrValues, 865 final ClaimsRequest claims, 866 final JWT requestObject, 867 final URI requestURI, 868 final CodeChallenge codeChallenge, 869 final CodeChallengeMethod codeChallengeMethod) { 870 871 this(uri, rt, rm, scope, clientID, redirectURI, state, 872 nonce, display, prompt, maxAge, uiLocales, claimsLocales, 873 idTokenHint, loginHint, acrValues, claims, 874 requestObject, requestURI, codeChallenge, codeChallengeMethod, 875 Collections.<String, String>emptyMap()); 876 } 877 878 879 /** 880 * Creates a new OpenID Connect authentication request with additional 881 * custom parameters. 882 * 883 * @param uri The URI of the OAuth 2.0 authorisation 884 * endpoint. May be {@code null} if the 885 * {@link #toHTTPRequest} method will not be 886 * used. 887 * @param rt The response type set. Corresponds to the 888 * {@code response_type} parameter. Must 889 * specify a valid OpenID Connect response 890 * type. Must not be {@code null}. 891 * @param rm The response mode. Corresponds to the 892 * optional {@code response_mode} parameter. 893 * Use of this parameter is not recommended 894 * unless a non-default response mode is 895 * requested (e.g. form_post). 896 * @param scope The request scope. Corresponds to the 897 * {@code scope} parameter. Must contain an 898 * {@link OIDCScopeValue#OPENID openid value}. 899 * Must not be {@code null}. 900 * @param clientID The client identifier. Corresponds to the 901 * {@code client_id} parameter. Must not be 902 * {@code null}. 903 * @param redirectURI The redirection URI. Corresponds to the 904 * {@code redirect_uri} parameter. Must not 905 * be {@code null} unless set by means of 906 * the optional {@code request_object} / 907 * {@code request_uri} parameter. 908 * @param state The state. Corresponds to the recommended 909 * {@code state} parameter. {@code null} if 910 * not specified. 911 * @param nonce The nonce. Corresponds to the 912 * {@code nonce} parameter. May be 913 * {@code null} for code flow. 914 * @param display The requested display type. Corresponds 915 * to the optional {@code display} 916 * parameter. 917 * {@code null} if not specified. 918 * @param prompt The requested prompt. Corresponds to the 919 * optional {@code prompt} parameter. 920 * {@code null} if not specified. 921 * @param maxAge The required maximum authentication age, 922 * in seconds. Corresponds to the optional 923 * {@code max_age} parameter. -1 if not 924 * specified, zero implies 925 * {@code prompt=login}. 926 * @param uiLocales The preferred languages and scripts for 927 * the user interface. Corresponds to the 928 * optional {@code ui_locales} parameter. 929 * {@code null} if not specified. 930 * @param claimsLocales The preferred languages and scripts for 931 * claims being returned. Corresponds to the 932 * optional {@code claims_locales} 933 * parameter. {@code null} if not specified. 934 * @param idTokenHint The ID Token hint. Corresponds to the 935 * optional {@code id_token_hint} parameter. 936 * {@code null} if not specified. 937 * @param loginHint The login hint. Corresponds to the 938 * optional {@code login_hint} parameter. 939 * {@code null} if not specified. 940 * @param acrValues The requested Authentication Context 941 * Class Reference values. Corresponds to 942 * the optional {@code acr_values} 943 * parameter. {@code null} if not specified. 944 * @param claims The individual claims to be returned. 945 * Corresponds to the optional 946 * {@code claims} parameter. {@code null} if 947 * not specified. 948 * @param requestObject The request object. Corresponds to the 949 * optional {@code request} parameter. Must 950 * not be specified together with a request 951 * object URI. {@code null} if not 952 * specified. 953 * @param requestURI The request object URI. Corresponds to 954 * the optional {@code request_uri} 955 * parameter. Must not be specified together 956 * with a request object. {@code null} if 957 * not specified. 958 * @param codeChallenge The code challenge for PKCE, {@code null} 959 * if not specified. 960 * @param codeChallengeMethod The code challenge method for PKCE, 961 * {@code null} if not specified. 962 * @param customParams Additional custom parameters, empty map 963 * or {@code null} if none. 964 */ 965 public AuthenticationRequest(final URI uri, 966 final ResponseType rt, 967 final ResponseMode rm, 968 final Scope scope, 969 final ClientID clientID, 970 final URI redirectURI, 971 final State state, 972 final Nonce nonce, 973 final Display display, 974 final Prompt prompt, 975 final int maxAge, 976 final List<LangTag> uiLocales, 977 final List<LangTag> claimsLocales, 978 final JWT idTokenHint, 979 final String loginHint, 980 final List<ACR> acrValues, 981 final ClaimsRequest claims, 982 final JWT requestObject, 983 final URI requestURI, 984 final CodeChallenge codeChallenge, 985 final CodeChallengeMethod codeChallengeMethod, 986 final Map<String,String> customParams) { 987 988 super(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 989 990 // Redirect URI required unless set in request_object / request_uri 991 if (redirectURI == null && requestObject == null && requestURI == null) 992 throw new IllegalArgumentException("The redirection URI must not be null"); 993 994 OIDCResponseTypeValidator.validate(rt); 995 996 if (scope == null) 997 throw new IllegalArgumentException("The scope must not be null"); 998 999 if (! scope.contains(OIDCScopeValue.OPENID)) 1000 throw new IllegalArgumentException("The scope must include an \"openid\" token"); 1001 1002 1003 // Nonce required in the implicit and hybrid flows 1004 if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow())) 1005 throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow"); 1006 1007 this.nonce = nonce; 1008 1009 // Optional parameters 1010 this.display = display; 1011 this.prompt = prompt; 1012 this.maxAge = maxAge; 1013 1014 if (uiLocales != null) 1015 this.uiLocales = Collections.unmodifiableList(uiLocales); 1016 else 1017 this.uiLocales = null; 1018 1019 if (claimsLocales != null) 1020 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 1021 else 1022 this.claimsLocales = null; 1023 1024 this.idTokenHint = idTokenHint; 1025 this.loginHint = loginHint; 1026 1027 if (acrValues != null) 1028 this.acrValues = Collections.unmodifiableList(acrValues); 1029 else 1030 this.acrValues = null; 1031 1032 this.claims = claims; 1033 1034 if (requestObject != null && requestURI != null) 1035 throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both"); 1036 1037 this.requestObject = requestObject; 1038 this.requestURI = requestURI; 1039 } 1040 1041 1042 /** 1043 * Returns the registered (standard) OpenID Connect authentication 1044 * request parameter names. 1045 * 1046 * @return The registered OpenID Connect authentication request 1047 * parameter names, as a unmodifiable set. 1048 */ 1049 public static Set<String> getRegisteredParameterNames() { 1050 1051 return REGISTERED_PARAMETER_NAMES; 1052 } 1053 1054 1055 /** 1056 * Gets the nonce. Corresponds to the conditionally optional 1057 * {@code nonce} parameter. 1058 * 1059 * @return The nonce, {@code null} if not specified. 1060 */ 1061 public Nonce getNonce() { 1062 1063 return nonce; 1064 } 1065 1066 1067 /** 1068 * Gets the requested display type. Corresponds to the optional 1069 * {@code display} parameter. 1070 * 1071 * @return The requested display type, {@code null} if not specified. 1072 */ 1073 public Display getDisplay() { 1074 1075 return display; 1076 } 1077 1078 1079 /** 1080 * Gets the requested prompt. Corresponds to the optional 1081 * {@code prompt} parameter. 1082 * 1083 * @return The requested prompt, {@code null} if not specified. 1084 */ 1085 public Prompt getPrompt() { 1086 1087 return prompt; 1088 } 1089 1090 1091 /** 1092 * Gets the required maximum authentication age. Corresponds to the 1093 * optional {@code max_age} parameter. 1094 * 1095 * @return The maximum authentication age, in seconds; -1 if not 1096 * specified, zero implies {@code prompt=login}. 1097 */ 1098 public int getMaxAge() { 1099 1100 return maxAge; 1101 } 1102 1103 1104 /** 1105 * Gets the end-user's preferred languages and scripts for the user 1106 * interface, ordered by preference. Corresponds to the optional 1107 * {@code ui_locales} parameter. 1108 * 1109 * @return The preferred UI locales, {@code null} if not specified. 1110 */ 1111 public List<LangTag> getUILocales() { 1112 1113 return uiLocales; 1114 } 1115 1116 1117 /** 1118 * Gets the end-user's preferred languages and scripts for the claims 1119 * being returned, ordered by preference. Corresponds to the optional 1120 * {@code claims_locales} parameter. 1121 * 1122 * @return The preferred claims locales, {@code null} if not specified. 1123 */ 1124 public List<LangTag> getClaimsLocales() { 1125 1126 return claimsLocales; 1127 } 1128 1129 1130 /** 1131 * Gets the ID Token hint. Corresponds to the conditionally optional 1132 * {@code id_token_hint} parameter. 1133 * 1134 * @return The ID Token hint, {@code null} if not specified. 1135 */ 1136 public JWT getIDTokenHint() { 1137 1138 return idTokenHint; 1139 } 1140 1141 1142 /** 1143 * Gets the login hint. Corresponds to the optional {@code login_hint} 1144 * parameter. 1145 * 1146 * @return The login hint, {@code null} if not specified. 1147 */ 1148 public String getLoginHint() { 1149 1150 return loginHint; 1151 } 1152 1153 1154 /** 1155 * Gets the requested Authentication Context Class Reference values. 1156 * Corresponds to the optional {@code acr_values} parameter. 1157 * 1158 * @return The requested ACR values, {@code null} if not specified. 1159 */ 1160 public List<ACR> getACRValues() { 1161 1162 return acrValues; 1163 } 1164 1165 1166 /** 1167 * Gets the individual claims to be returned. Corresponds to the 1168 * optional {@code claims} parameter. 1169 * 1170 * @return The individual claims to be returned, {@code null} if not 1171 * specified. 1172 */ 1173 public ClaimsRequest getClaims() { 1174 1175 return claims; 1176 } 1177 1178 1179 /** 1180 * Gets the request object. Corresponds to the optional {@code request} 1181 * parameter. 1182 * 1183 * @return The request object, {@code null} if not specified. 1184 */ 1185 public JWT getRequestObject() { 1186 1187 return requestObject; 1188 } 1189 1190 1191 /** 1192 * Gets the request object URI. Corresponds to the optional 1193 * {@code request_uri} parameter. 1194 * 1195 * @return The request object URI, {@code null} if not specified. 1196 */ 1197 public URI getRequestURI() { 1198 1199 return requestURI; 1200 } 1201 1202 1203 /** 1204 * Returns {@code true} if this authentication request specifies an 1205 * OpenID Connect request object (directly through the {@code request} 1206 * parameter or by reference through the {@code request_uri} parameter). 1207 * 1208 * @return {@code true} if a request object is specified, else 1209 * {@code false}. 1210 */ 1211 public boolean specifiesRequestObject() { 1212 1213 return requestObject != null || requestURI != null; 1214 } 1215 1216 1217 @Override 1218 public Map<String,String> toParameters() { 1219 1220 Map <String,String> params = super.toParameters(); 1221 1222 if (nonce != null) 1223 params.put("nonce", nonce.toString()); 1224 1225 if (display != null) 1226 params.put("display", display.toString()); 1227 1228 if (prompt != null) 1229 params.put("prompt", prompt.toString()); 1230 1231 if (maxAge >= 0) 1232 params.put("max_age", "" + maxAge); 1233 1234 if (uiLocales != null) { 1235 1236 StringBuilder sb = new StringBuilder(); 1237 1238 for (LangTag locale: uiLocales) { 1239 1240 if (sb.length() > 0) 1241 sb.append(' '); 1242 1243 sb.append(locale.toString()); 1244 } 1245 1246 params.put("ui_locales", sb.toString()); 1247 } 1248 1249 if (claimsLocales != null) { 1250 1251 StringBuilder sb = new StringBuilder(); 1252 1253 for (LangTag locale: claimsLocales) { 1254 1255 if (sb.length() > 0) 1256 sb.append(' '); 1257 1258 sb.append(locale.toString()); 1259 } 1260 1261 params.put("claims_locales", sb.toString()); 1262 } 1263 1264 if (idTokenHint != null) { 1265 1266 try { 1267 params.put("id_token_hint", idTokenHint.serialize()); 1268 1269 } catch (IllegalStateException e) { 1270 1271 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 1272 } 1273 } 1274 1275 if (loginHint != null) 1276 params.put("login_hint", loginHint); 1277 1278 if (acrValues != null) { 1279 1280 StringBuilder sb = new StringBuilder(); 1281 1282 for (ACR acr: acrValues) { 1283 1284 if (sb.length() > 0) 1285 sb.append(' '); 1286 1287 sb.append(acr.toString()); 1288 } 1289 1290 params.put("acr_values", sb.toString()); 1291 } 1292 1293 1294 if (claims != null) 1295 params.put("claims", claims.toJSONObject().toString()); 1296 1297 if (requestObject != null) { 1298 1299 try { 1300 params.put("request", requestObject.serialize()); 1301 1302 } catch (IllegalStateException e) { 1303 1304 throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e); 1305 } 1306 } 1307 1308 if (requestURI != null) 1309 params.put("request_uri", requestURI.toString()); 1310 1311 return params; 1312 } 1313 1314 1315 /** 1316 * Parses an OpenID Connect authentication request from the specified 1317 * parameters. 1318 * 1319 * <p>Example parameters: 1320 * 1321 * <pre> 1322 * response_type = token id_token 1323 * client_id = s6BhdRkqt3 1324 * redirect_uri = https://client.example.com/cb 1325 * scope = openid profile 1326 * state = af0ifjsldkj 1327 * nonce = -0S6_WzA2Mj 1328 * </pre> 1329 * 1330 * @param params The parameters. Must not be {@code null}. 1331 * 1332 * @return The OpenID Connect authentication request. 1333 * 1334 * @throws ParseException If the parameters couldn't be parsed to an 1335 * OpenID Connect authentication request. 1336 */ 1337 public static AuthenticationRequest parse(final Map<String,String> params) 1338 throws ParseException { 1339 1340 return parse(null, params); 1341 } 1342 1343 1344 /** 1345 * Parses an OpenID Connect authentication request from the specified 1346 * parameters. 1347 * 1348 * <p>Example parameters: 1349 * 1350 * <pre> 1351 * response_type = token id_token 1352 * client_id = s6BhdRkqt3 1353 * redirect_uri = https://client.example.com/cb 1354 * scope = openid profile 1355 * state = af0ifjsldkj 1356 * nonce = -0S6_WzA2Mj 1357 * </pre> 1358 * 1359 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May 1360 * be {@code null} if the {@link #toHTTPRequest} method 1361 * will not be used. 1362 * @param params The parameters. Must not be {@code null}. 1363 * 1364 * @return The OpenID Connect authentication request. 1365 * 1366 * @throws ParseException If the parameters couldn't be parsed to an 1367 * OpenID Connect authentication request. 1368 */ 1369 public static AuthenticationRequest parse(final URI uri, final Map<String,String> params) 1370 throws ParseException { 1371 1372 // Parse and validate the core OAuth 2.0 autz request params in 1373 // the context of OIDC 1374 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 1375 1376 ClientID clientID = ar.getClientID(); 1377 State state = ar.getState(); 1378 ResponseMode rm = ar.getResponseMode(); 1379 1380 // Required in OIDC, check later after optional request_object / request_uri is parsed 1381 URI redirectURI = ar.getRedirectionURI(); 1382 1383 ResponseType rt = ar.getResponseType(); 1384 1385 try { 1386 OIDCResponseTypeValidator.validate(rt); 1387 1388 } catch (IllegalArgumentException e) { 1389 String msg = "Unsupported \"response_type\" parameter: " + e.getMessage(); 1390 throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg), 1391 clientID, redirectURI, ar.impliedResponseMode(), state); 1392 } 1393 1394 // Required in OIDC, must include "openid" parameter 1395 Scope scope = ar.getScope(); 1396 1397 if (scope == null) { 1398 String msg = "Missing \"scope\" parameter"; 1399 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1400 clientID, redirectURI, ar.impliedResponseMode(), state); 1401 } 1402 1403 if (! scope.contains(OIDCScopeValue.OPENID)) { 1404 String msg = "The scope must include an \"openid\" value"; 1405 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1406 clientID, redirectURI, ar.impliedResponseMode(), state); 1407 } 1408 1409 1410 // Parse the remaining OIDC parameters 1411 Nonce nonce = Nonce.parse(params.get("nonce")); 1412 1413 // Nonce required in the implicit and hybrid flows 1414 if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow())) { 1415 String msg = "Missing \"nonce\" parameter: Required in the implicit and hybrid flows"; 1416 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1417 clientID, redirectURI, ar.impliedResponseMode(), state); 1418 } 1419 1420 Display display = null; 1421 1422 if (params.containsKey("display")) { 1423 try { 1424 display = Display.parse(params.get("display")); 1425 1426 } catch (ParseException e) { 1427 String msg = "Invalid \"display\" parameter: " + e.getMessage(); 1428 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1429 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1430 } 1431 } 1432 1433 1434 Prompt prompt; 1435 1436 try { 1437 prompt = Prompt.parse(params.get("prompt")); 1438 1439 } catch (ParseException e) { 1440 String msg = "Invalid \"prompt\" parameter: " + e.getMessage(); 1441 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1442 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1443 } 1444 1445 1446 String v = params.get("max_age"); 1447 1448 int maxAge = -1; 1449 1450 if (StringUtils.isNotBlank(v)) { 1451 1452 try { 1453 maxAge = Integer.parseInt(v); 1454 1455 } catch (NumberFormatException e) { 1456 String msg = "Invalid \"max_age\" parameter: " + v; 1457 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1458 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1459 } 1460 } 1461 1462 1463 v = params.get("ui_locales"); 1464 1465 List<LangTag> uiLocales = null; 1466 1467 if (StringUtils.isNotBlank(v)) { 1468 1469 uiLocales = new LinkedList<>(); 1470 1471 StringTokenizer st = new StringTokenizer(v, " "); 1472 1473 while (st.hasMoreTokens()) { 1474 1475 try { 1476 uiLocales.add(LangTag.parse(st.nextToken())); 1477 1478 } catch (LangTagException e) { 1479 String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage(); 1480 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1481 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1482 } 1483 } 1484 } 1485 1486 1487 v = params.get("claims_locales"); 1488 1489 List<LangTag> claimsLocales = null; 1490 1491 if (StringUtils.isNotBlank(v)) { 1492 1493 claimsLocales = new LinkedList<>(); 1494 1495 StringTokenizer st = new StringTokenizer(v, " "); 1496 1497 while (st.hasMoreTokens()) { 1498 1499 try { 1500 claimsLocales.add(LangTag.parse(st.nextToken())); 1501 1502 } catch (LangTagException e) { 1503 String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage(); 1504 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1505 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1506 } 1507 } 1508 } 1509 1510 1511 v = params.get("id_token_hint"); 1512 1513 JWT idTokenHint = null; 1514 1515 if (StringUtils.isNotBlank(v)) { 1516 1517 try { 1518 idTokenHint = JWTParser.parse(v); 1519 1520 } catch (java.text.ParseException e) { 1521 String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage(); 1522 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1523 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1524 } 1525 } 1526 1527 String loginHint = params.get("login_hint"); 1528 1529 1530 v = params.get("acr_values"); 1531 1532 List<ACR> acrValues = null; 1533 1534 if (StringUtils.isNotBlank(v)) { 1535 1536 acrValues = new LinkedList<>(); 1537 1538 StringTokenizer st = new StringTokenizer(v, " "); 1539 1540 while (st.hasMoreTokens()) { 1541 1542 acrValues.add(new ACR(st.nextToken())); 1543 } 1544 } 1545 1546 1547 v = params.get("claims"); 1548 1549 ClaimsRequest claims = null; 1550 1551 if (StringUtils.isNotBlank(v)) { 1552 1553 JSONObject jsonObject; 1554 1555 try { 1556 jsonObject = JSONObjectUtils.parse(v); 1557 1558 } catch (ParseException e) { 1559 String msg = "Invalid \"claims\" parameter: " + e.getMessage(); 1560 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1561 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1562 } 1563 1564 // Parse exceptions silently ignored 1565 claims = ClaimsRequest.parse(jsonObject); 1566 } 1567 1568 1569 v = params.get("request_uri"); 1570 1571 URI requestURI = null; 1572 1573 if (StringUtils.isNotBlank(v)) { 1574 1575 try { 1576 requestURI = new URI(v); 1577 1578 } catch (URISyntaxException e) { 1579 String msg = "Invalid \"request_uri\" parameter: " + e.getMessage(); 1580 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1581 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1582 } 1583 } 1584 1585 v = params.get("request"); 1586 1587 JWT requestObject = null; 1588 1589 if (StringUtils.isNotBlank(v)) { 1590 1591 // request_object and request_uri must not be defined at the same time 1592 if (requestURI != null) { 1593 String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters"; 1594 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1595 clientID, redirectURI, ar.impliedResponseMode(), state, null); 1596 } 1597 1598 try { 1599 requestObject = JWTParser.parse(v); 1600 1601 } catch (java.text.ParseException e) { 1602 String msg = "Invalid \"request_object\" parameter: " + e.getMessage(); 1603 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1604 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1605 } 1606 } 1607 1608 1609 // Redirect URI required unless request_object / request_uri present 1610 if (redirectURI == null && requestObject == null && requestURI == null) { 1611 String msg = "Missing \"redirect_uri\" parameter"; 1612 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1613 clientID, null, ar.impliedResponseMode(), state); 1614 } 1615 1616 // Parse additional custom parameters 1617 Map<String,String> customParams = null; 1618 1619 for (Map.Entry<String,String> p: params.entrySet()) { 1620 1621 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1622 // We have a custom parameter 1623 if (customParams == null) { 1624 customParams = new HashMap<>(); 1625 } 1626 customParams.put(p.getKey(), p.getValue()); 1627 } 1628 } 1629 1630 1631 return new AuthenticationRequest( 1632 uri, rt, rm, scope, clientID, redirectURI, state, nonce, 1633 display, prompt, maxAge, uiLocales, claimsLocales, 1634 idTokenHint, loginHint, acrValues, claims, requestObject, requestURI, 1635 ar.getCodeChallenge(), ar.getCodeChallengeMethod(), 1636 customParams); 1637 } 1638 1639 1640 /** 1641 * Parses an OpenID Connect authentication request from the specified 1642 * URI query string. 1643 * 1644 * <p>Example URI query string: 1645 * 1646 * <pre> 1647 * response_type=token%20id_token 1648 * &client_id=s6BhdRkqt3 1649 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1650 * &scope=openid%20profile 1651 * &state=af0ifjsldkj 1652 * &nonce=n-0S6_WzA2Mj 1653 * </pre> 1654 * 1655 * @param query The URI query string. Must not be {@code null}. 1656 * 1657 * @return The OpenID Connect authentication request. 1658 * 1659 * @throws ParseException If the query string couldn't be parsed to an 1660 * OpenID Connect authentication request. 1661 */ 1662 public static AuthenticationRequest parse(final String query) 1663 throws ParseException { 1664 1665 return parse(null, URLUtils.parseParameters(query)); 1666 } 1667 1668 1669 /** 1670 * Parses an OpenID Connect authentication request from the specified 1671 * URI query string. 1672 * 1673 * <p>Example URI query string: 1674 * 1675 * <pre> 1676 * response_type=token%20id_token 1677 * &client_id=s6BhdRkqt3 1678 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1679 * &scope=openid%20profile 1680 * &state=af0ifjsldkj 1681 * &nonce=n-0S6_WzA2Mj 1682 * </pre> 1683 * 1684 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May be 1685 * {@code null} if the {@link #toHTTPRequest} method will 1686 * not be used. 1687 * @param query The URI query string. Must not be {@code null}. 1688 * 1689 * @return The OpenID Connect authentication request. 1690 * 1691 * @throws ParseException If the query string couldn't be parsed to an 1692 * OpenID Connect authentication request. 1693 */ 1694 public static AuthenticationRequest parse(final URI uri, final String query) 1695 throws ParseException { 1696 1697 return parse(uri, URLUtils.parseParameters(query)); 1698 } 1699 1700 1701 /** 1702 * Parses an OpenID Connect authentication request from the specified 1703 * URI. 1704 * 1705 * <p>Example URI: 1706 * 1707 * <pre> 1708 * https://server.example.com/authorize? 1709 * response_type=token%20id_token 1710 * &client_id=s6BhdRkqt3 1711 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1712 * &scope=openid%20profile 1713 * &state=af0ifjsldkj 1714 * &nonce=n-0S6_WzA2Mj 1715 * </pre> 1716 * 1717 * @param uri The URI. Must not be {@code null}. 1718 * 1719 * @return The OpenID Connect authentication request. 1720 * 1721 * @throws ParseException If the query string couldn't be parsed to an 1722 * OpenID Connect authentication request. 1723 */ 1724 public static AuthenticationRequest parse(final URI uri) 1725 throws ParseException { 1726 1727 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1728 } 1729 1730 1731 /** 1732 * Parses an authentication request from the specified HTTP GET or HTTP 1733 * POST request. 1734 * 1735 * <p>Example HTTP request (GET): 1736 * 1737 * <pre> 1738 * https://server.example.com/op/authorize? 1739 * response_type=code%20id_token 1740 * &client_id=s6BhdRkqt3 1741 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1742 * &scope=openid 1743 * &nonce=n-0S6_WzA2Mj 1744 * &state=af0ifjsldkj 1745 * </pre> 1746 * 1747 * @param httpRequest The HTTP request. Must not be {@code null}. 1748 * 1749 * @return The OpenID Connect authentication request. 1750 * 1751 * @throws ParseException If the HTTP request couldn't be parsed to an 1752 * OpenID Connect authentication request. 1753 */ 1754 public static AuthenticationRequest parse(final HTTPRequest httpRequest) 1755 throws ParseException { 1756 1757 String query = httpRequest.getQuery(); 1758 1759 if (query == null) 1760 throw new ParseException("Missing URI query string"); 1761 1762 URI endpointURI; 1763 1764 try { 1765 endpointURI = httpRequest.getURL().toURI(); 1766 1767 } catch (URISyntaxException e) { 1768 1769 throw new ParseException(e.getMessage(), e); 1770 } 1771 1772 return parse(endpointURI, query); 1773 } 1774}