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