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