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