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 914 Map <String,String> params = super.toParameters(); 915 916 if (nonce != null) 917 params.put("nonce", nonce.toString()); 918 919 if (display != null) 920 params.put("display", display.toString()); 921 922 if (prompt != null) 923 params.put("prompt", prompt.toString()); 924 925 if (maxAge > 0) 926 params.put("max_age", "" + maxAge); 927 928 if (uiLocales != null) { 929 930 StringBuilder sb = new StringBuilder(); 931 932 for (LangTag locale: uiLocales) { 933 934 if (sb.length() > 0) 935 sb.append(' '); 936 937 sb.append(locale.toString()); 938 } 939 940 params.put("ui_locales", sb.toString()); 941 } 942 943 if (claimsLocales != null) { 944 945 StringBuilder sb = new StringBuilder(); 946 947 for (LangTag locale: claimsLocales) { 948 949 if (sb.length() > 0) 950 sb.append(' '); 951 952 sb.append(locale.toString()); 953 } 954 955 params.put("claims_locales", sb.toString()); 956 } 957 958 if (idTokenHint != null) { 959 960 try { 961 params.put("id_token_hint", idTokenHint.serialize()); 962 963 } catch (IllegalStateException e) { 964 965 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 966 } 967 } 968 969 if (loginHint != null) 970 params.put("login_hint", loginHint); 971 972 if (acrValues != null) { 973 974 StringBuilder sb = new StringBuilder(); 975 976 for (ACR acr: acrValues) { 977 978 if (sb.length() > 0) 979 sb.append(' '); 980 981 sb.append(acr.toString()); 982 } 983 984 params.put("acr_values", sb.toString()); 985 } 986 987 988 if (claims != null) 989 params.put("claims", claims.toJSONObject().toString()); 990 991 if (requestObject != null) { 992 993 try { 994 params.put("request", requestObject.serialize()); 995 996 } catch (IllegalStateException e) { 997 998 throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e); 999 } 1000 } 1001 1002 if (requestURI != null) 1003 params.put("request_uri", requestURI.toString()); 1004 1005 return params; 1006 } 1007 1008 1009 /** 1010 * Parses an OpenID Connect authentication request from the specified 1011 * parameters. 1012 * 1013 * <p>Example parameters: 1014 * 1015 * <pre> 1016 * response_type = token id_token 1017 * client_id = s6BhdRkqt3 1018 * redirect_uri = https://client.example.com/cb 1019 * scope = openid profile 1020 * state = af0ifjsldkj 1021 * nonce = -0S6_WzA2Mj 1022 * </pre> 1023 * 1024 * @param params The parameters. Must not be {@code null}. 1025 * 1026 * @return The OpenID Connect authentication request. 1027 * 1028 * @throws ParseException If the parameters couldn't be parsed to an 1029 * OpenID Connect authentication request. 1030 */ 1031 public static AuthenticationRequest parse(final Map<String,String> params) 1032 throws ParseException { 1033 1034 return parse(null, params); 1035 } 1036 1037 1038 /** 1039 * Parses an OpenID Connect authentication request from the specified 1040 * parameters. 1041 * 1042 * <p>Example parameters: 1043 * 1044 * <pre> 1045 * response_type = token id_token 1046 * client_id = s6BhdRkqt3 1047 * redirect_uri = https://client.example.com/cb 1048 * scope = openid profile 1049 * state = af0ifjsldkj 1050 * nonce = -0S6_WzA2Mj 1051 * </pre> 1052 * 1053 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May 1054 * be {@code null} if the {@link #toHTTPRequest} method 1055 * will not be used. 1056 * @param params The parameters. Must not be {@code null}. 1057 * 1058 * @return The OpenID Connect authentication request. 1059 * 1060 * @throws ParseException If the parameters couldn't be parsed to an 1061 * OpenID Connect authentication request. 1062 */ 1063 public static AuthenticationRequest parse(final URI uri, final Map<String,String> params) 1064 throws ParseException { 1065 1066 // Parse and validate the core OAuth 2.0 autz request params in 1067 // the context of OIDC 1068 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 1069 1070 ClientID clientID = ar.getClientID(); 1071 State state = ar.getState(); 1072 ResponseMode rm = ar.getResponseMode(); 1073 1074 // Required in OIDC, check later after optional request_object / request_uri is parsed 1075 URI redirectURI = ar.getRedirectionURI(); 1076 1077 ResponseType rt = ar.getResponseType(); 1078 1079 try { 1080 OIDCResponseTypeValidator.validate(rt); 1081 1082 } catch (IllegalArgumentException e) { 1083 String msg = "Unsupported \"response_type\" parameter: " + e.getMessage(); 1084 throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg), 1085 clientID, redirectURI, ar.impliedResponseMode(), state); 1086 } 1087 1088 // Required in OIDC, must include "openid" parameter 1089 Scope scope = ar.getScope(); 1090 1091 if (scope == null) { 1092 String msg = "Missing \"scope\" parameter"; 1093 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1094 clientID, redirectURI, ar.impliedResponseMode(), state); 1095 } 1096 1097 if (! scope.contains(OIDCScopeValue.OPENID)) { 1098 String msg = "The scope must include an \"openid\" value"; 1099 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1100 clientID, redirectURI, ar.impliedResponseMode(), state); 1101 } 1102 1103 1104 // Parse the remaining OIDC parameters 1105 Nonce nonce = Nonce.parse(params.get("nonce")); 1106 1107 // Nonce required in implicit flow 1108 if (rt.impliesImplicitFlow() && nonce == null) { 1109 String msg = "Missing \"nonce\" parameter: Required in implicit flow"; 1110 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1111 clientID, redirectURI, ar.impliedResponseMode(), state); 1112 } 1113 1114 Display display; 1115 1116 try { 1117 display = Display.parse(params.get("display")); 1118 1119 } catch (ParseException e) { 1120 String msg = "Invalid \"display\" parameter: " + e.getMessage(); 1121 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1122 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1123 } 1124 1125 1126 Prompt prompt; 1127 1128 try { 1129 prompt = Prompt.parse(params.get("prompt")); 1130 1131 } catch (ParseException e) { 1132 String msg = "Invalid \"prompt\" parameter: " + e.getMessage(); 1133 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1134 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1135 } 1136 1137 1138 String v = params.get("max_age"); 1139 1140 int maxAge = 0; 1141 1142 if (StringUtils.isNotBlank(v)) { 1143 1144 try { 1145 maxAge = Integer.parseInt(v); 1146 1147 } catch (NumberFormatException e) { 1148 String msg = "Invalid \"max_age\" parameter: " + v; 1149 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1150 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1151 } 1152 } 1153 1154 1155 v = params.get("ui_locales"); 1156 1157 List<LangTag> uiLocales = null; 1158 1159 if (StringUtils.isNotBlank(v)) { 1160 1161 uiLocales = new LinkedList<>(); 1162 1163 StringTokenizer st = new StringTokenizer(v, " "); 1164 1165 while (st.hasMoreTokens()) { 1166 1167 try { 1168 uiLocales.add(LangTag.parse(st.nextToken())); 1169 1170 } catch (LangTagException e) { 1171 String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage(); 1172 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1173 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1174 } 1175 } 1176 } 1177 1178 1179 v = params.get("claims_locales"); 1180 1181 List<LangTag> claimsLocales = null; 1182 1183 if (StringUtils.isNotBlank(v)) { 1184 1185 claimsLocales = new LinkedList<>(); 1186 1187 StringTokenizer st = new StringTokenizer(v, " "); 1188 1189 while (st.hasMoreTokens()) { 1190 1191 try { 1192 claimsLocales.add(LangTag.parse(st.nextToken())); 1193 1194 } catch (LangTagException e) { 1195 String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage(); 1196 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1197 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1198 } 1199 } 1200 } 1201 1202 1203 v = params.get("id_token_hint"); 1204 1205 JWT idTokenHint = null; 1206 1207 if (StringUtils.isNotBlank(v)) { 1208 1209 try { 1210 idTokenHint = JWTParser.parse(v); 1211 1212 } catch (java.text.ParseException e) { 1213 String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage(); 1214 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1215 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1216 } 1217 } 1218 1219 String loginHint = params.get("login_hint"); 1220 1221 1222 v = params.get("acr_values"); 1223 1224 List<ACR> acrValues = null; 1225 1226 if (StringUtils.isNotBlank(v)) { 1227 1228 acrValues = new LinkedList<>(); 1229 1230 StringTokenizer st = new StringTokenizer(v, " "); 1231 1232 while (st.hasMoreTokens()) { 1233 1234 acrValues.add(new ACR(st.nextToken())); 1235 } 1236 } 1237 1238 1239 v = params.get("claims"); 1240 1241 ClaimsRequest claims = null; 1242 1243 if (StringUtils.isNotBlank(v)) { 1244 1245 JSONObject jsonObject; 1246 1247 try { 1248 jsonObject = JSONObjectUtils.parse(v); 1249 1250 } catch (ParseException e) { 1251 String msg = "Invalid \"claims\" parameter: " + e.getMessage(); 1252 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1253 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1254 } 1255 1256 // Parse exceptions silently ignored 1257 claims = ClaimsRequest.parse(jsonObject); 1258 } 1259 1260 1261 v = params.get("request_uri"); 1262 1263 URI requestURI = null; 1264 1265 if (StringUtils.isNotBlank(v)) { 1266 1267 try { 1268 requestURI = new URI(v); 1269 1270 } catch (URISyntaxException e) { 1271 String msg = "Invalid \"request_uri\" parameter: " + e.getMessage(); 1272 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1273 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1274 } 1275 } 1276 1277 v = params.get("request"); 1278 1279 JWT requestObject = null; 1280 1281 if (StringUtils.isNotBlank(v)) { 1282 1283 // request_object and request_uri must not be defined at the same time 1284 if (requestURI != null) { 1285 String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters"; 1286 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1287 clientID, redirectURI, ar.impliedResponseMode(), state, null); 1288 } 1289 1290 try { 1291 requestObject = JWTParser.parse(v); 1292 1293 } catch (java.text.ParseException e) { 1294 String msg = "Invalid \"request_object\" parameter: " + e.getMessage(); 1295 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1296 clientID, redirectURI, ar.impliedResponseMode(), state, e); 1297 } 1298 } 1299 1300 1301 // Redirect URI required unless request_object / request_uri present 1302 if (redirectURI == null && requestObject == null && requestURI == null) { 1303 String msg = "Missing \"redirect_uri\" parameter"; 1304 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1305 clientID, null, ar.impliedResponseMode(), state); 1306 } 1307 1308 1309 return new AuthenticationRequest( 1310 uri, rt, rm, scope, clientID, redirectURI, state, nonce, 1311 display, prompt, maxAge, uiLocales, claimsLocales, 1312 idTokenHint, loginHint, acrValues, claims, requestObject, requestURI); 1313 } 1314 1315 1316 /** 1317 * Parses an OpenID Connect authentication request from the specified 1318 * URI query string. 1319 * 1320 * <p>Example URI query string: 1321 * 1322 * <pre> 1323 * response_type=token%20id_token 1324 * &client_id=s6BhdRkqt3 1325 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1326 * &scope=openid%20profile 1327 * &state=af0ifjsldkj 1328 * &nonce=n-0S6_WzA2Mj 1329 * </pre> 1330 * 1331 * @param query The URI query string. Must not be {@code null}. 1332 * 1333 * @return The OpenID Connect authentication request. 1334 * 1335 * @throws ParseException If the query string couldn't be parsed to an 1336 * OpenID Connect authentication request. 1337 */ 1338 public static AuthenticationRequest parse(final String query) 1339 throws ParseException { 1340 1341 return parse(null, URLUtils.parseParameters(query)); 1342 } 1343 1344 1345 /** 1346 * Parses an OpenID Connect authentication request from the specified 1347 * URI query string. 1348 * 1349 * <p>Example URI query string: 1350 * 1351 * <pre> 1352 * response_type=token%20id_token 1353 * &client_id=s6BhdRkqt3 1354 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1355 * &scope=openid%20profile 1356 * &state=af0ifjsldkj 1357 * &nonce=n-0S6_WzA2Mj 1358 * </pre> 1359 * 1360 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May be 1361 * {@code null} if the {@link #toHTTPRequest} method will 1362 * not be used. 1363 * @param query The URI query string. Must not be {@code null}. 1364 * 1365 * @return The OpenID Connect authentication request. 1366 * 1367 * @throws ParseException If the query string couldn't be parsed to an 1368 * OpenID Connect authentication request. 1369 */ 1370 public static AuthenticationRequest parse(final URI uri, final String query) 1371 throws ParseException { 1372 1373 return parse(uri, URLUtils.parseParameters(query)); 1374 } 1375 1376 1377 /** 1378 * Parses an OpenID Connect authentication request from the specified 1379 * URI. 1380 * 1381 * <p>Example URI: 1382 * 1383 * <pre> 1384 * https://server.example.com/authorize? 1385 * response_type=token%20id_token 1386 * &client_id=s6BhdRkqt3 1387 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1388 * &scope=openid%20profile 1389 * &state=af0ifjsldkj 1390 * &nonce=n-0S6_WzA2Mj 1391 * </pre> 1392 * 1393 * @param uri The URI. Must not be {@code null}. 1394 * 1395 * @return The OpenID Connect authentication request. 1396 * 1397 * @throws ParseException If the query string couldn't be parsed to an 1398 * OpenID Connect authentication request. 1399 */ 1400 public static AuthenticationRequest parse(final URI uri) 1401 throws ParseException { 1402 1403 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1404 } 1405 1406 1407 /** 1408 * Parses an authentication request from the specified HTTP GET or HTTP 1409 * POST request. 1410 * 1411 * <p>Example HTTP request (GET): 1412 * 1413 * <pre> 1414 * https://server.example.com/op/authorize? 1415 * response_type=code%20id_token 1416 * &client_id=s6BhdRkqt3 1417 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1418 * &scope=openid 1419 * &nonce=n-0S6_WzA2Mj 1420 * &state=af0ifjsldkj 1421 * </pre> 1422 * 1423 * @param httpRequest The HTTP request. Must not be {@code null}. 1424 * 1425 * @return The OpenID Connect authentication request. 1426 * 1427 * @throws ParseException If the HTTP request couldn't be parsed to an 1428 * OpenID Connect authentication request. 1429 */ 1430 public static AuthenticationRequest parse(final HTTPRequest httpRequest) 1431 throws ParseException { 1432 1433 String query = httpRequest.getQuery(); 1434 1435 if (query == null) 1436 throw new ParseException("Missing URI query string"); 1437 1438 URI endpointURI; 1439 1440 try { 1441 endpointURI = httpRequest.getURL().toURI(); 1442 1443 } catch (URISyntaxException e) { 1444 1445 throw new ParseException(e.getMessage(), e); 1446 } 1447 1448 return parse(endpointURI, query); 1449 } 1450}