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 * Sets the state. Corresponds to the recommended {@code state} 384 * parameter. 385 * 386 * @param state The state, {@code null} if not specified. 387 * 388 * @return This builder. 389 */ 390 public Builder state(final State state) { 391 392 this.state = state; 393 return this; 394 } 395 396 397 /** 398 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 399 * request is intended. 400 * 401 * @param uri The endpoint URI, {@code null} if not specified. 402 * 403 * @return This builder. 404 */ 405 public Builder endpointURI(final URI uri) { 406 407 this.uri = uri; 408 return this; 409 } 410 411 412 /** 413 * Sets the nonce. Corresponds to the conditionally optional 414 * {@code nonce} parameter. 415 * 416 * @param nonce The nonce, {@code null} if not specified. 417 */ 418 public Builder nonce(final Nonce nonce) { 419 420 this.nonce = nonce; 421 return this; 422 } 423 424 425 /** 426 * Sets the requested display type. Corresponds to the optional 427 * {@code display} parameter. 428 * 429 * @param display The requested display type, {@code null} if 430 * not specified. 431 */ 432 public Builder display(final Display display) { 433 434 this.display = display; 435 return this; 436 } 437 438 439 /** 440 * Sets the requested prompt. Corresponds to the optional 441 * {@code prompt} parameter. 442 * 443 * @param prompt The requested prompt, {@code null} if not 444 * specified. 445 */ 446 public Builder prompt(final Prompt prompt) { 447 448 this.prompt = prompt; 449 return this; 450 } 451 452 453 /** 454 * Sets the required maximum authentication age. Corresponds to 455 * the optional {@code max_age} parameter. 456 * 457 * @param maxAge The maximum authentication age, in seconds; 0 458 * if not specified. 459 */ 460 public Builder maxAge(final int maxAge) { 461 462 this.maxAge = maxAge; 463 return this; 464 } 465 466 467 /** 468 * Sets the end-user's preferred languages and scripts for the 469 * user interface, ordered by preference. Corresponds to the 470 * optional {@code ui_locales} parameter. 471 * 472 * @param uiLocales The preferred UI locales, {@code null} if 473 * not specified. 474 */ 475 public Builder uiLocales(final List<LangTag> uiLocales) { 476 477 this.uiLocales = uiLocales; 478 return this; 479 } 480 481 482 /** 483 * Sets the end-user's preferred languages and scripts for the 484 * claims being returned, ordered by preference. Corresponds to 485 * the optional {@code claims_locales} parameter. 486 * 487 * @param claimsLocales The preferred claims locales, 488 * {@code null} if not specified. 489 */ 490 public Builder claimsLocales(final List<LangTag> claimsLocales) { 491 492 this.claimsLocales = claimsLocales; 493 return this; 494 } 495 496 497 /** 498 * Sets the ID Token hint. Corresponds to the conditionally 499 * optional {@code id_token_hint} parameter. 500 * 501 * @param idTokenHint The ID Token hint, {@code null} if not 502 * specified. 503 */ 504 public Builder idTokenHint(final JWT idTokenHint) { 505 506 this.idTokenHint = idTokenHint; 507 return this; 508 } 509 510 511 /** 512 * Sets the login hint. Corresponds to the optional 513 * {@code login_hint} parameter. 514 * 515 * @param loginHint The login hint, {@code null} if not 516 * specified. 517 */ 518 public Builder loginHint(final String loginHint) { 519 520 this.loginHint = loginHint; 521 return this; 522 } 523 524 525 /** 526 * Sets the requested Authentication Context Class Reference 527 * values. Corresponds to the optional {@code acr_values} 528 * parameter. 529 * 530 * @param acrValues The requested ACR values, {@code null} if 531 * not specified. 532 */ 533 public Builder acrValues(final List<ACR> acrValues) { 534 535 this.acrValues = acrValues; 536 return this; 537 } 538 539 540 /** 541 * Sets the individual claims to be returned. Corresponds to 542 * the optional {@code claims} parameter. 543 * 544 * @param claims The individual claims to be returned, 545 * {@code null} if not specified. 546 */ 547 public Builder claims(final ClaimsRequest claims) { 548 549 this.claims = claims; 550 return this; 551 } 552 553 554 /** 555 * Sets the request object. Corresponds to the optional 556 * {@code request} parameter. Must not be specified together 557 * with a request object URI. 558 * 559 * @return The request object, {@code null} if not specified. 560 */ 561 public Builder requestObject(final JWT requestObject) { 562 563 this.requestObject = requestObject; 564 return this; 565 } 566 567 568 /** 569 * Sets the request object URI. Corresponds to the optional 570 * {@code request_uri} parameter. Must not be specified 571 * together with a request object. 572 * 573 * @param requestURI The request object URI, {@code null} if 574 * not specified. 575 */ 576 public Builder requestURI(final URI requestURI) { 577 578 this.requestURI = requestURI; 579 return this; 580 } 581 582 583 /** 584 * Sets the response mode. Corresponds to the optional 585 * {@code response_mode} parameter. Use of this parameter is 586 * not recommended unless a non-default response mode is 587 * requested (e.g. form_post). 588 * 589 * @param rm The response mode, {@code null} if not specified. 590 * 591 * @return This builder. 592 */ 593 public Builder responseMode(final ResponseMode rm) { 594 595 this.rm = rm; 596 return this; 597 } 598 599 600 /** 601 * Sets the code challenge for Proof Key for Code Exchange 602 * (PKCE) by public OAuth clients. 603 * 604 * @param codeChallenge The code challenge, {@code null} 605 * if not specified. 606 * @param codeChallengeMethod The code challenge method, 607 * {@code null} if not specified. 608 * 609 * @return This builder. 610 */ 611 @Deprecated 612 public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) { 613 614 this.codeChallenge = codeChallenge; 615 this.codeChallengeMethod = codeChallengeMethod; 616 return this; 617 } 618 619 620 /** 621 * Sets the code challenge for Proof Key for Code Exchange 622 * (PKCE) by public OAuth clients. 623 * 624 * @param codeVerifier The code verifier to use to 625 * compute the code challenge, 626 * {@code null} if PKCE is not 627 * specified. 628 * @param codeChallengeMethod The code challenge method, 629 * {@code null} if not specified. 630 * Defaults to 631 * {@link CodeChallengeMethod#PLAIN} 632 * if a code verifier is specified. 633 * 634 * @return This builder. 635 */ 636 public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) { 637 638 if (codeVerifier != null) { 639 CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault(); 640 this.codeChallenge = CodeChallenge.compute(method, codeVerifier); 641 this.codeChallengeMethod = method; 642 } else { 643 this.codeChallenge = null; 644 this.codeChallengeMethod = null; 645 } 646 return this; 647 } 648 649 650 /** 651 * Sets the specified additional custom parameter. 652 * 653 * @param name The parameter name. Must not be {@code null}. 654 * @param value The parameter value, {@code null} if not 655 * specified. 656 * 657 * @return This builder. 658 */ 659 public Builder customParameter(final String name, final String value) { 660 661 customParams.put(name, value); 662 return this; 663 } 664 665 666 /** 667 * Builds a new authentication request. 668 * 669 * @return The authentication request. 670 */ 671 public AuthenticationRequest build() { 672 673 try { 674 return new AuthenticationRequest( 675 uri, rt, rm, scope, clientID, redirectURI, state, nonce, 676 display, prompt, maxAge, uiLocales, claimsLocales, 677 idTokenHint, loginHint, acrValues, claims, 678 requestObject, requestURI, 679 codeChallenge, codeChallengeMethod, 680 customParams); 681 682 } catch (IllegalArgumentException e) { 683 684 throw new IllegalStateException(e.getMessage(), e); 685 } 686 } 687 } 688 689 690 /** 691 * Creates a new minimal OpenID Connect authentication request. 692 * 693 * @param uri The URI of the OAuth 2.0 authorisation endpoint. 694 * May be {@code null} if the {@link #toHTTPRequest} 695 * method will not be used. 696 * @param rt The response type. Corresponds to the 697 * {@code response_type} parameter. Must specify a 698 * valid OpenID Connect response type. Must not be 699 * {@code null}. 700 * @param scope The request scope. Corresponds to the 701 * {@code scope} parameter. Must contain an 702 * {@link OIDCScopeValue#OPENID openid value}. Must 703 * not be {@code null}. 704 * @param clientID The client identifier. Corresponds to the 705 * {@code client_id} parameter. Must not be 706 * {@code null}. 707 * @param redirectURI The redirection URI. Corresponds to the 708 * {@code redirect_uri} parameter. Must not be 709 * {@code null}. 710 * @param state The state. Corresponds to the {@code state} 711 * parameter. May be {@code null}. 712 * @param nonce The nonce. Corresponds to the {@code nonce} 713 * parameter. May be {@code null} for code flow. 714 */ 715 public AuthenticationRequest(final URI uri, 716 final ResponseType rt, 717 final Scope scope, 718 final ClientID clientID, 719 final URI redirectURI, 720 final State state, 721 final Nonce nonce) { 722 723 // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 724 // idTokenHint, loginHint, acrValues, claims 725 // codeChallenge, codeChallengeMethod 726 this(uri, rt, null, scope, clientID, redirectURI, state, nonce, 727 null, null, -1, null, null, 728 null, null, null, null, null, null, 729 null, null); 730 } 731 732 733 /** 734 * Creates a new OpenID Connect authentication request. 735 * 736 * @param uri The URI of the OAuth 2.0 authorisation 737 * endpoint. May be {@code null} if the 738 * {@link #toHTTPRequest} method will not be 739 * used. 740 * @param rt The response type set. Corresponds to the 741 * {@code response_type} parameter. Must 742 * specify a valid OpenID Connect response 743 * type. Must not be {@code null}. 744 * @param rm The response mode. Corresponds to the 745 * optional {@code response_mode} parameter. 746 * Use of this parameter is not recommended 747 * unless a non-default response mode is 748 * requested (e.g. form_post). 749 * @param scope The request scope. Corresponds to the 750 * {@code scope} parameter. Must contain an 751 * {@link OIDCScopeValue#OPENID openid value}. 752 * Must not be {@code null}. 753 * @param clientID The client identifier. Corresponds to the 754 * {@code client_id} parameter. Must not be 755 * {@code null}. 756 * @param redirectURI The redirection URI. Corresponds to the 757 * {@code redirect_uri} parameter. Must not 758 * be {@code null} unless set by means of 759 * the optional {@code request_object} / 760 * {@code request_uri} parameter. 761 * @param state The state. Corresponds to the recommended 762 * {@code state} parameter. {@code null} if 763 * not specified. 764 * @param nonce The nonce. Corresponds to the 765 * {@code nonce} parameter. May be 766 * {@code null} for code flow. 767 * @param display The requested display type. Corresponds 768 * to the optional {@code display} 769 * parameter. 770 * {@code null} if not specified. 771 * @param prompt The requested prompt. Corresponds to the 772 * optional {@code prompt} parameter. 773 * {@code null} if not specified. 774 * @param maxAge The required maximum authentication age, 775 * in seconds. Corresponds to the optional 776 * {@code max_age} parameter. Zero if not 777 * specified. 778 * @param uiLocales The preferred languages and scripts for 779 * the user interface. Corresponds to the 780 * optional {@code ui_locales} parameter. 781 * {@code null} if not specified. 782 * @param claimsLocales The preferred languages and scripts for 783 * claims being returned. Corresponds to the 784 * optional {@code claims_locales} 785 * parameter. {@code null} if not specified. 786 * @param idTokenHint The ID Token hint. Corresponds to the 787 * optional {@code id_token_hint} parameter. 788 * {@code null} if not specified. 789 * @param loginHint The login hint. Corresponds to the 790 * optional {@code login_hint} parameter. 791 * {@code null} if not specified. 792 * @param acrValues The requested Authentication Context 793 * Class Reference values. Corresponds to 794 * the optional {@code acr_values} 795 * parameter. {@code null} if not specified. 796 * @param claims The individual claims to be returned. 797 * Corresponds to the optional 798 * {@code claims} parameter. {@code null} if 799 * not specified. 800 * @param requestObject The request object. Corresponds to the 801 * optional {@code request} parameter. Must 802 * not be specified together with a request 803 * object URI. {@code null} if not 804 * specified. 805 * @param requestURI The request object URI. Corresponds to 806 * the optional {@code request_uri} 807 * parameter. Must not be specified together 808 * with a request object. {@code null} if 809 * not specified. 810 * @param codeChallenge The code challenge for PKCE, {@code null} 811 * if not specified. 812 * @param codeChallengeMethod The code challenge method for PKCE, 813 * {@code null} if not specified. 814 */ 815 public AuthenticationRequest(final URI uri, 816 final ResponseType rt, 817 final ResponseMode rm, 818 final Scope scope, 819 final ClientID clientID, 820 final URI redirectURI, 821 final State state, 822 final Nonce nonce, 823 final Display display, 824 final Prompt prompt, 825 final int maxAge, 826 final List<LangTag> uiLocales, 827 final List<LangTag> claimsLocales, 828 final JWT idTokenHint, 829 final String loginHint, 830 final List<ACR> acrValues, 831 final ClaimsRequest claims, 832 final JWT requestObject, 833 final URI requestURI, 834 final CodeChallenge codeChallenge, 835 final CodeChallengeMethod codeChallengeMethod) { 836 837 this(uri, rt, rm, scope, clientID, redirectURI, state, 838 nonce, display, prompt, maxAge, uiLocales, claimsLocales, 839 idTokenHint, loginHint, acrValues, claims, 840 requestObject, requestURI, codeChallenge, codeChallengeMethod, 841 Collections.<String, String>emptyMap()); 842 } 843 844 845 /** 846 * Creates a new OpenID Connect authentication request with additional 847 * custom parameters. 848 * 849 * @param uri The URI of the OAuth 2.0 authorisation 850 * endpoint. May be {@code null} if the 851 * {@link #toHTTPRequest} method will not be 852 * used. 853 * @param rt The response type set. Corresponds to the 854 * {@code response_type} parameter. Must 855 * specify a valid OpenID Connect response 856 * type. Must not be {@code null}. 857 * @param rm The response mode. Corresponds to the 858 * optional {@code response_mode} parameter. 859 * Use of this parameter is not recommended 860 * unless a non-default response mode is 861 * requested (e.g. form_post). 862 * @param scope The request scope. Corresponds to the 863 * {@code scope} parameter. Must contain an 864 * {@link OIDCScopeValue#OPENID openid value}. 865 * Must not be {@code null}. 866 * @param clientID The client identifier. Corresponds to the 867 * {@code client_id} parameter. Must not be 868 * {@code null}. 869 * @param redirectURI The redirection URI. Corresponds to the 870 * {@code redirect_uri} parameter. Must not 871 * be {@code null} unless set by means of 872 * the optional {@code request_object} / 873 * {@code request_uri} parameter. 874 * @param state The state. Corresponds to the recommended 875 * {@code state} parameter. {@code null} if 876 * not specified. 877 * @param nonce The nonce. Corresponds to the 878 * {@code nonce} parameter. May be 879 * {@code null} for code flow. 880 * @param display The requested display type. Corresponds 881 * to the optional {@code display} 882 * parameter. 883 * {@code null} if not specified. 884 * @param prompt The requested prompt. Corresponds to the 885 * optional {@code prompt} parameter. 886 * {@code null} if not specified. 887 * @param maxAge The required maximum authentication age, 888 * in seconds. Corresponds to the optional 889 * {@code max_age} parameter. -1 if not 890 * specified, zero implies 891 * {@code prompt=login}. 892 * @param uiLocales The preferred languages and scripts for 893 * the user interface. Corresponds to the 894 * optional {@code ui_locales} parameter. 895 * {@code null} if not specified. 896 * @param claimsLocales The preferred languages and scripts for 897 * claims being returned. Corresponds to the 898 * optional {@code claims_locales} 899 * parameter. {@code null} if not specified. 900 * @param idTokenHint The ID Token hint. Corresponds to the 901 * optional {@code id_token_hint} parameter. 902 * {@code null} if not specified. 903 * @param loginHint The login hint. Corresponds to the 904 * optional {@code login_hint} parameter. 905 * {@code null} if not specified. 906 * @param acrValues The requested Authentication Context 907 * Class Reference values. Corresponds to 908 * the optional {@code acr_values} 909 * parameter. {@code null} if not specified. 910 * @param claims The individual claims to be returned. 911 * Corresponds to the optional 912 * {@code claims} parameter. {@code null} if 913 * not specified. 914 * @param requestObject The request object. Corresponds to the 915 * optional {@code request} parameter. Must 916 * not be specified together with a request 917 * object URI. {@code null} if not 918 * specified. 919 * @param requestURI The request object URI. Corresponds to 920 * the optional {@code request_uri} 921 * parameter. Must not be specified together 922 * with a request object. {@code null} if 923 * not specified. 924 * @param codeChallenge The code challenge for PKCE, {@code null} 925 * if not specified. 926 * @param codeChallengeMethod The code challenge method for PKCE, 927 * {@code null} if not specified. 928 * @param customParams Additional custom parameters, empty map 929 * or {@code null} if none. 930 */ 931 public AuthenticationRequest(final URI uri, 932 final ResponseType rt, 933 final ResponseMode rm, 934 final Scope scope, 935 final ClientID clientID, 936 final URI redirectURI, 937 final State state, 938 final Nonce nonce, 939 final Display display, 940 final Prompt prompt, 941 final int maxAge, 942 final List<LangTag> uiLocales, 943 final List<LangTag> claimsLocales, 944 final JWT idTokenHint, 945 final String loginHint, 946 final List<ACR> acrValues, 947 final ClaimsRequest claims, 948 final JWT requestObject, 949 final URI requestURI, 950 final CodeChallenge codeChallenge, 951 final CodeChallengeMethod codeChallengeMethod, 952 final Map<String,String> customParams) { 953 954 super(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams); 955 956 // Redirect URI required unless set in request_object / request_uri 957 if (redirectURI == null && requestObject == null && requestURI == null) 958 throw new IllegalArgumentException("The redirection URI must not be null"); 959 960 OIDCResponseTypeValidator.validate(rt); 961 962 if (scope == null) 963 throw new IllegalArgumentException("The scope must not be null"); 964 965 if (! scope.contains(OIDCScopeValue.OPENID)) 966 throw new IllegalArgumentException("The scope must include an \"openid\" token"); 967 968 969 // Nonce required for implicit protocol flow 970 if (rt.impliesImplicitFlow() && nonce == null) 971 throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow"); 972 973 this.nonce = nonce; 974 975 // Optional parameters 976 this.display = display; 977 this.prompt = prompt; 978 this.maxAge = maxAge; 979 980 if (uiLocales != null) 981 this.uiLocales = Collections.unmodifiableList(uiLocales); 982 else 983 this.uiLocales = null; 984 985 if (claimsLocales != null) 986 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 987 else 988 this.claimsLocales = null; 989 990 this.idTokenHint = idTokenHint; 991 this.loginHint = loginHint; 992 993 if (acrValues != null) 994 this.acrValues = Collections.unmodifiableList(acrValues); 995 else 996 this.acrValues = null; 997 998 this.claims = claims; 999 1000 if (requestObject != null && requestURI != null) 1001 throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both"); 1002 1003 this.requestObject = requestObject; 1004 this.requestURI = requestURI; 1005 } 1006 1007 1008 /** 1009 * Returns the registered (standard) OpenID Connect authentication 1010 * request parameter names. 1011 * 1012 * @return The registered OpenID Connect authentication request 1013 * parameter names, as a unmodifiable set. 1014 */ 1015 public static Set<String> getRegisteredParameterNames() { 1016 1017 return REGISTERED_PARAMETER_NAMES; 1018 } 1019 1020 1021 /** 1022 * Gets the nonce. Corresponds to the conditionally optional 1023 * {@code nonce} parameter. 1024 * 1025 * @return The nonce, {@code null} if not specified. 1026 */ 1027 public Nonce getNonce() { 1028 1029 return nonce; 1030 } 1031 1032 1033 /** 1034 * Gets the requested display type. Corresponds to the optional 1035 * {@code display} parameter. 1036 * 1037 * @return The requested display type, {@code null} if not specified. 1038 */ 1039 public Display getDisplay() { 1040 1041 return display; 1042 } 1043 1044 1045 /** 1046 * Gets the requested prompt. Corresponds to the optional 1047 * {@code prompt} parameter. 1048 * 1049 * @return The requested prompt, {@code null} if not specified. 1050 */ 1051 public Prompt getPrompt() { 1052 1053 return prompt; 1054 } 1055 1056 1057 /** 1058 * Gets the required maximum authentication age. Corresponds to the 1059 * optional {@code max_age} parameter. 1060 * 1061 * @return The maximum authentication age, in seconds; -1 if not 1062 * specified, zero implies {@code prompt=login}. 1063 */ 1064 public int getMaxAge() { 1065 1066 return maxAge; 1067 } 1068 1069 1070 /** 1071 * Gets the end-user's preferred languages and scripts for the user 1072 * interface, ordered by preference. Corresponds to the optional 1073 * {@code ui_locales} parameter. 1074 * 1075 * @return The preferred UI locales, {@code null} if not specified. 1076 */ 1077 public List<LangTag> getUILocales() { 1078 1079 return uiLocales; 1080 } 1081 1082 1083 /** 1084 * Gets the end-user's preferred languages and scripts for the claims 1085 * being returned, ordered by preference. Corresponds to the optional 1086 * {@code claims_locales} parameter. 1087 * 1088 * @return The preferred claims locales, {@code null} if not specified. 1089 */ 1090 public List<LangTag> getClaimsLocales() { 1091 1092 return claimsLocales; 1093 } 1094 1095 1096 /** 1097 * Gets the ID Token hint. Corresponds to the conditionally optional 1098 * {@code id_token_hint} parameter. 1099 * 1100 * @return The ID Token hint, {@code null} if not specified. 1101 */ 1102 public JWT getIDTokenHint() { 1103 1104 return idTokenHint; 1105 } 1106 1107 1108 /** 1109 * Gets the login hint. Corresponds to the optional {@code login_hint} 1110 * parameter. 1111 * 1112 * @return The login hint, {@code null} if not specified. 1113 */ 1114 public String getLoginHint() { 1115 1116 return loginHint; 1117 } 1118 1119 1120 /** 1121 * Gets the requested Authentication Context Class Reference values. 1122 * Corresponds to the optional {@code acr_values} parameter. 1123 * 1124 * @return The requested ACR values, {@code null} if not specified. 1125 */ 1126 public List<ACR> getACRValues() { 1127 1128 return acrValues; 1129 } 1130 1131 1132 /** 1133 * Gets the individual claims to be returned. Corresponds to the 1134 * optional {@code claims} parameter. 1135 * 1136 * @return The individual claims to be returned, {@code null} if not 1137 * specified. 1138 */ 1139 public ClaimsRequest getClaims() { 1140 1141 return claims; 1142 } 1143 1144 1145 /** 1146 * Gets the request object. Corresponds to the optional {@code request} 1147 * parameter. 1148 * 1149 * @return The request object, {@code null} if not specified. 1150 */ 1151 public JWT getRequestObject() { 1152 1153 return requestObject; 1154 } 1155 1156 1157 /** 1158 * Gets the request object URI. Corresponds to the optional 1159 * {@code request_uri} parameter. 1160 * 1161 * @return The request object URI, {@code null} if not specified. 1162 */ 1163 public URI getRequestURI() { 1164 1165 return requestURI; 1166 } 1167 1168 1169 /** 1170 * Returns {@code true} if this authentication request specifies an 1171 * OpenID Connect request object (directly through the {@code request} 1172 * parameter or by reference through the {@code request_uri} parameter). 1173 * 1174 * @return {@code true} if a request object is specified, else 1175 * {@code false}. 1176 */ 1177 public boolean specifiesRequestObject() { 1178 1179 return requestObject != null || requestURI != null; 1180 } 1181 1182 1183 @Override 1184 public Map<String,String> toParameters() { 1185 1186 Map <String,String> params = super.toParameters(); 1187 1188 if (nonce != null) 1189 params.put("nonce", nonce.toString()); 1190 1191 if (display != null) 1192 params.put("display", display.toString()); 1193 1194 if (prompt != null) 1195 params.put("prompt", prompt.toString()); 1196 1197 if (maxAge >= 0) 1198 params.put("max_age", "" + maxAge); 1199 1200 if (uiLocales != null) { 1201 1202 StringBuilder sb = new StringBuilder(); 1203 1204 for (LangTag locale: uiLocales) { 1205 1206 if (sb.length() > 0) 1207 sb.append(' '); 1208 1209 sb.append(locale.toString()); 1210 } 1211 1212 params.put("ui_locales", sb.toString()); 1213 } 1214 1215 if (claimsLocales != null) { 1216 1217 StringBuilder sb = new StringBuilder(); 1218 1219 for (LangTag locale: claimsLocales) { 1220 1221 if (sb.length() > 0) 1222 sb.append(' '); 1223 1224 sb.append(locale.toString()); 1225 } 1226 1227 params.put("claims_locales", sb.toString()); 1228 } 1229 1230 if (idTokenHint != null) { 1231 1232 try { 1233 params.put("id_token_hint", idTokenHint.serialize()); 1234 1235 } catch (IllegalStateException e) { 1236 1237 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 1238 } 1239 } 1240 1241 if (loginHint != null) 1242 params.put("login_hint", loginHint); 1243 1244 if (acrValues != null) { 1245 1246 StringBuilder sb = new StringBuilder(); 1247 1248 for (ACR acr: acrValues) { 1249 1250 if (sb.length() > 0) 1251 sb.append(' '); 1252 1253 sb.append(acr.toString()); 1254 } 1255 1256 params.put("acr_values", sb.toString()); 1257 } 1258 1259 1260 if (claims != null) 1261 params.put("claims", claims.toJSONObject().toString()); 1262 1263 if (requestObject != null) { 1264 1265 try { 1266 params.put("request", requestObject.serialize()); 1267 1268 } catch (IllegalStateException e) { 1269 1270 throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e); 1271 } 1272 } 1273 1274 if (requestURI != null) 1275 params.put("request_uri", requestURI.toString()); 1276 1277 return params; 1278 } 1279 1280 1281 /** 1282 * Parses an OpenID Connect authentication request from the specified 1283 * parameters. 1284 * 1285 * <p>Example parameters: 1286 * 1287 * <pre> 1288 * response_type = token id_token 1289 * client_id = s6BhdRkqt3 1290 * redirect_uri = https://client.example.com/cb 1291 * scope = openid profile 1292 * state = af0ifjsldkj 1293 * nonce = -0S6_WzA2Mj 1294 * </pre> 1295 * 1296 * @param params The parameters. Must not be {@code null}. 1297 * 1298 * @return The OpenID Connect authentication request. 1299 * 1300 * @throws ParseException If the parameters couldn't be parsed to an 1301 * OpenID Connect authentication request. 1302 */ 1303 public static AuthenticationRequest parse(final Map<String,String> params) 1304 throws ParseException { 1305 1306 return parse(null, params); 1307 } 1308 1309 1310 /** 1311 * Parses an OpenID Connect authentication request from the specified 1312 * parameters. 1313 * 1314 * <p>Example parameters: 1315 * 1316 * <pre> 1317 * response_type = token id_token 1318 * client_id = s6BhdRkqt3 1319 * redirect_uri = https://client.example.com/cb 1320 * scope = openid profile 1321 * state = af0ifjsldkj 1322 * nonce = -0S6_WzA2Mj 1323 * </pre> 1324 * 1325 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May 1326 * be {@code null} if the {@link #toHTTPRequest} method 1327 * will not be used. 1328 * @param params The parameters. Must not be {@code null}. 1329 * 1330 * @return The OpenID Connect authentication request. 1331 * 1332 * @throws ParseException If the parameters couldn't be parsed to an 1333 * OpenID Connect authentication request. 1334 */ 1335 public static AuthenticationRequest parse(final URI uri, final Map<String,String> params) 1336 throws ParseException { 1337 1338 // Parse and validate the core OAuth 2.0 autz request params in 1339 // the context of OIDC 1340 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 1341 1342 ClientID clientID = ar.getClientID(); 1343 State state = ar.getState(); 1344 ResponseMode rm = ar.getResponseMode(); 1345 1346 // Required in OIDC, check later after optional request_object / request_uri is parsed 1347 URI redirectURI = ar.getRedirectionURI(); 1348 1349 ResponseType rt = ar.getResponseType(); 1350 1351 try { 1352 OIDCResponseTypeValidator.validate(rt); 1353 1354 } catch (IllegalArgumentException e) { 1355 String msg = "Unsupported \"response_type\" parameter: " + e.getMessage(); 1356 throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg), 1357 clientID, redirectURI, ar.impliedResponseMode(), state); 1358 } 1359 1360 // Required in OIDC, must include "openid" parameter 1361 Scope scope = ar.getScope(); 1362 1363 if (scope == null) { 1364 String msg = "Missing \"scope\" parameter"; 1365 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1366 clientID, redirectURI, ar.impliedResponseMode(), state); 1367 } 1368 1369 if (! scope.contains(OIDCScopeValue.OPENID)) { 1370 String msg = "The scope must include an \"openid\" value"; 1371 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1372 clientID, redirectURI, ar.impliedResponseMode(), state); 1373 } 1374 1375 1376 // Parse the remaining OIDC parameters 1377 Nonce nonce = Nonce.parse(params.get("nonce")); 1378 1379 // Nonce required in implicit flow 1380 if (rt.impliesImplicitFlow() && nonce == null) { 1381 String msg = "Missing \"nonce\" parameter: Required in implicit flow"; 1382 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1383 clientID, redirectURI, ar.impliedResponseMode(), state); 1384 } 1385 1386 Display display = null; 1387 1388 if (params.containsKey("display")) { 1389 try { 1390 display = Display.parse(params.get("display")); 1391 1392 } catch (ParseException e) { 1393 String msg = "Invalid \"display\" parameter: " + e.getMessage(); 1394 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1395 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1396 } 1397 } 1398 1399 1400 Prompt prompt; 1401 1402 try { 1403 prompt = Prompt.parse(params.get("prompt")); 1404 1405 } catch (ParseException e) { 1406 String msg = "Invalid \"prompt\" parameter: " + e.getMessage(); 1407 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1408 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1409 } 1410 1411 1412 String v = params.get("max_age"); 1413 1414 int maxAge = -1; 1415 1416 if (StringUtils.isNotBlank(v)) { 1417 1418 try { 1419 maxAge = Integer.parseInt(v); 1420 1421 } catch (NumberFormatException e) { 1422 String msg = "Invalid \"max_age\" parameter: " + v; 1423 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1424 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1425 } 1426 } 1427 1428 1429 v = params.get("ui_locales"); 1430 1431 List<LangTag> uiLocales = null; 1432 1433 if (StringUtils.isNotBlank(v)) { 1434 1435 uiLocales = new LinkedList<>(); 1436 1437 StringTokenizer st = new StringTokenizer(v, " "); 1438 1439 while (st.hasMoreTokens()) { 1440 1441 try { 1442 uiLocales.add(LangTag.parse(st.nextToken())); 1443 1444 } catch (LangTagException e) { 1445 String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage(); 1446 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1447 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1448 } 1449 } 1450 } 1451 1452 1453 v = params.get("claims_locales"); 1454 1455 List<LangTag> claimsLocales = null; 1456 1457 if (StringUtils.isNotBlank(v)) { 1458 1459 claimsLocales = new LinkedList<>(); 1460 1461 StringTokenizer st = new StringTokenizer(v, " "); 1462 1463 while (st.hasMoreTokens()) { 1464 1465 try { 1466 claimsLocales.add(LangTag.parse(st.nextToken())); 1467 1468 } catch (LangTagException e) { 1469 String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage(); 1470 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1471 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1472 } 1473 } 1474 } 1475 1476 1477 v = params.get("id_token_hint"); 1478 1479 JWT idTokenHint = null; 1480 1481 if (StringUtils.isNotBlank(v)) { 1482 1483 try { 1484 idTokenHint = JWTParser.parse(v); 1485 1486 } catch (java.text.ParseException e) { 1487 String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage(); 1488 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1489 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1490 } 1491 } 1492 1493 String loginHint = params.get("login_hint"); 1494 1495 1496 v = params.get("acr_values"); 1497 1498 List<ACR> acrValues = null; 1499 1500 if (StringUtils.isNotBlank(v)) { 1501 1502 acrValues = new LinkedList<>(); 1503 1504 StringTokenizer st = new StringTokenizer(v, " "); 1505 1506 while (st.hasMoreTokens()) { 1507 1508 acrValues.add(new ACR(st.nextToken())); 1509 } 1510 } 1511 1512 1513 v = params.get("claims"); 1514 1515 ClaimsRequest claims = null; 1516 1517 if (StringUtils.isNotBlank(v)) { 1518 1519 JSONObject jsonObject; 1520 1521 try { 1522 jsonObject = JSONObjectUtils.parse(v); 1523 1524 } catch (ParseException e) { 1525 String msg = "Invalid \"claims\" parameter: " + e.getMessage(); 1526 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1527 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1528 } 1529 1530 // Parse exceptions silently ignored 1531 claims = ClaimsRequest.parse(jsonObject); 1532 } 1533 1534 1535 v = params.get("request_uri"); 1536 1537 URI requestURI = null; 1538 1539 if (StringUtils.isNotBlank(v)) { 1540 1541 try { 1542 requestURI = new URI(v); 1543 1544 } catch (URISyntaxException e) { 1545 String msg = "Invalid \"request_uri\" parameter: " + e.getMessage(); 1546 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1547 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1548 } 1549 } 1550 1551 v = params.get("request"); 1552 1553 JWT requestObject = null; 1554 1555 if (StringUtils.isNotBlank(v)) { 1556 1557 // request_object and request_uri must not be defined at the same time 1558 if (requestURI != null) { 1559 String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters"; 1560 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1561 clientID, redirectURI, ar.impliedResponseMode(), state, null); 1562 } 1563 1564 try { 1565 requestObject = JWTParser.parse(v); 1566 1567 } catch (java.text.ParseException e) { 1568 String msg = "Invalid \"request_object\" parameter: " + e.getMessage(); 1569 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1570 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1571 } 1572 } 1573 1574 1575 // Redirect URI required unless request_object / request_uri present 1576 if (redirectURI == null && requestObject == null && requestURI == null) { 1577 String msg = "Missing \"redirect_uri\" parameter"; 1578 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1579 clientID, null, ar.impliedResponseMode(), state); 1580 } 1581 1582 // Parse additional custom parameters 1583 Map<String,String> customParams = null; 1584 1585 for (Map.Entry<String,String> p: params.entrySet()) { 1586 1587 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1588 // We have a custom parameter 1589 if (customParams == null) { 1590 customParams = new HashMap<>(); 1591 } 1592 customParams.put(p.getKey(), p.getValue()); 1593 } 1594 } 1595 1596 1597 return new AuthenticationRequest( 1598 uri, rt, rm, scope, clientID, redirectURI, state, nonce, 1599 display, prompt, maxAge, uiLocales, claimsLocales, 1600 idTokenHint, loginHint, acrValues, claims, requestObject, requestURI, 1601 ar.getCodeChallenge(), ar.getCodeChallengeMethod(), 1602 customParams); 1603 } 1604 1605 1606 /** 1607 * Parses an OpenID Connect authentication request from the specified 1608 * URI query string. 1609 * 1610 * <p>Example URI query string: 1611 * 1612 * <pre> 1613 * response_type=token%20id_token 1614 * &client_id=s6BhdRkqt3 1615 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1616 * &scope=openid%20profile 1617 * &state=af0ifjsldkj 1618 * &nonce=n-0S6_WzA2Mj 1619 * </pre> 1620 * 1621 * @param query The URI query string. Must not be {@code null}. 1622 * 1623 * @return The OpenID Connect authentication request. 1624 * 1625 * @throws ParseException If the query string couldn't be parsed to an 1626 * OpenID Connect authentication request. 1627 */ 1628 public static AuthenticationRequest parse(final String query) 1629 throws ParseException { 1630 1631 return parse(null, URLUtils.parseParameters(query)); 1632 } 1633 1634 1635 /** 1636 * Parses an OpenID Connect authentication request from the specified 1637 * URI query string. 1638 * 1639 * <p>Example URI query string: 1640 * 1641 * <pre> 1642 * response_type=token%20id_token 1643 * &client_id=s6BhdRkqt3 1644 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1645 * &scope=openid%20profile 1646 * &state=af0ifjsldkj 1647 * &nonce=n-0S6_WzA2Mj 1648 * </pre> 1649 * 1650 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May be 1651 * {@code null} if the {@link #toHTTPRequest} method will 1652 * not be used. 1653 * @param query The URI query string. Must not be {@code null}. 1654 * 1655 * @return The OpenID Connect authentication request. 1656 * 1657 * @throws ParseException If the query string couldn't be parsed to an 1658 * OpenID Connect authentication request. 1659 */ 1660 public static AuthenticationRequest parse(final URI uri, final String query) 1661 throws ParseException { 1662 1663 return parse(uri, URLUtils.parseParameters(query)); 1664 } 1665 1666 1667 /** 1668 * Parses an OpenID Connect authentication request from the specified 1669 * URI. 1670 * 1671 * <p>Example URI: 1672 * 1673 * <pre> 1674 * https://server.example.com/authorize? 1675 * response_type=token%20id_token 1676 * &client_id=s6BhdRkqt3 1677 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1678 * &scope=openid%20profile 1679 * &state=af0ifjsldkj 1680 * &nonce=n-0S6_WzA2Mj 1681 * </pre> 1682 * 1683 * @param uri The URI. Must not be {@code null}. 1684 * 1685 * @return The OpenID Connect authentication request. 1686 * 1687 * @throws ParseException If the query string couldn't be parsed to an 1688 * OpenID Connect authentication request. 1689 */ 1690 public static AuthenticationRequest parse(final URI uri) 1691 throws ParseException { 1692 1693 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1694 } 1695 1696 1697 /** 1698 * Parses an authentication request from the specified HTTP GET or HTTP 1699 * POST request. 1700 * 1701 * <p>Example HTTP request (GET): 1702 * 1703 * <pre> 1704 * https://server.example.com/op/authorize? 1705 * response_type=code%20id_token 1706 * &client_id=s6BhdRkqt3 1707 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1708 * &scope=openid 1709 * &nonce=n-0S6_WzA2Mj 1710 * &state=af0ifjsldkj 1711 * </pre> 1712 * 1713 * @param httpRequest The HTTP request. Must not be {@code null}. 1714 * 1715 * @return The OpenID Connect authentication request. 1716 * 1717 * @throws ParseException If the HTTP request couldn't be parsed to an 1718 * OpenID Connect authentication request. 1719 */ 1720 public static AuthenticationRequest parse(final HTTPRequest httpRequest) 1721 throws ParseException { 1722 1723 String query = httpRequest.getQuery(); 1724 1725 if (query == null) 1726 throw new ParseException("Missing URI query string"); 1727 1728 URI endpointURI; 1729 1730 try { 1731 endpointURI = httpRequest.getURL().toURI(); 1732 1733 } catch (URISyntaxException e) { 1734 1735 throw new ParseException(e.getMessage(), e); 1736 } 1737 1738 return parse(endpointURI, query); 1739 } 1740}