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 1527 StringBuilder sb = new StringBuilder(); 1528 1529 for (LangTag locale: uiLocales) { 1530 1531 if (sb.length() > 0) 1532 sb.append(' '); 1533 1534 sb.append(locale.toString()); 1535 } 1536 1537 params.put("ui_locales", Collections.singletonList(sb.toString())); 1538 } 1539 1540 if (CollectionUtils.isNotEmpty(claimsLocales)) { 1541 1542 params.put("claims_locales", Collections.singletonList(LangTagUtils.concat(claimsLocales))); 1543 } 1544 1545 if (idTokenHint != null) { 1546 1547 try { 1548 params.put("id_token_hint", Collections.singletonList(idTokenHint.serialize())); 1549 1550 } catch (IllegalStateException e) { 1551 1552 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 1553 } 1554 } 1555 1556 if (loginHint != null) 1557 params.put("login_hint", Collections.singletonList(loginHint)); 1558 1559 if (acrValues != null) { 1560 1561 StringBuilder sb = new StringBuilder(); 1562 1563 for (ACR acr: acrValues) { 1564 1565 if (sb.length() > 0) 1566 sb.append(' '); 1567 1568 sb.append(acr.toString()); 1569 } 1570 1571 params.put("acr_values", Collections.singletonList(sb.toString())); 1572 } 1573 1574 1575 if (claims != null) 1576 params.put("claims", Collections.singletonList(claims.toJSONObject().toString())); 1577 1578 if (purpose != null) 1579 params.put("purpose", Collections.singletonList(purpose)); 1580 1581 return params; 1582 } 1583 1584 1585 @Override 1586 public JWTClaimsSet toJWTClaimsSet() { 1587 1588 JWTClaimsSet jwtClaimsSet = super.toJWTClaimsSet(); 1589 1590 if (jwtClaimsSet.getClaim("max_age") != null) { 1591 // Convert max_age to number in JSON object 1592 try { 1593 String maxAgeString = jwtClaimsSet.getStringClaim("max_age"); 1594 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(jwtClaimsSet); 1595 builder.claim("max_age", Integer.parseInt(maxAgeString)); 1596 return builder.build(); 1597 } catch (java.text.ParseException e) { 1598 throw new SerializeException(e.getMessage()); 1599 } 1600 } 1601 1602 return jwtClaimsSet; 1603 } 1604 1605 1606 /** 1607 * Parses an OpenID Connect authentication request from the specified 1608 * URI query parameters. 1609 * 1610 * <p>Example parameters: 1611 * 1612 * <pre> 1613 * response_type = token id_token 1614 * client_id = s6BhdRkqt3 1615 * redirect_uri = https://client.example.com/cb 1616 * scope = openid profile 1617 * state = af0ifjsldkj 1618 * nonce = -0S6_WzA2Mj 1619 * </pre> 1620 * 1621 * @param params The parameters. Must not be {@code null}. 1622 * 1623 * @return The OpenID Connect authentication request. 1624 * 1625 * @throws ParseException If the parameters couldn't be parsed to an 1626 * OpenID Connect authentication request. 1627 */ 1628 public static AuthenticationRequest parse(final Map<String,List<String>> params) 1629 throws ParseException { 1630 1631 return parse(null, params); 1632 } 1633 1634 1635 /** 1636 * Parses an OpenID Connect authentication request from the specified 1637 * URI and query parameters. 1638 * 1639 * <p>Example parameters: 1640 * 1641 * <pre> 1642 * response_type = token id_token 1643 * client_id = s6BhdRkqt3 1644 * redirect_uri = https://client.example.com/cb 1645 * scope = openid profile 1646 * state = af0ifjsldkj 1647 * nonce = -0S6_WzA2Mj 1648 * </pre> 1649 * 1650 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May 1651 * be {@code null} if the {@link #toHTTPRequest} method 1652 * will not be used. 1653 * @param params The parameters. Must not be {@code null}. 1654 * 1655 * @return The OpenID Connect authentication request. 1656 * 1657 * @throws ParseException If the parameters couldn't be parsed to an 1658 * OpenID Connect authentication request. 1659 */ 1660 public static AuthenticationRequest parse(final URI uri, final Map<String,List<String>> params) 1661 throws ParseException { 1662 1663 // Parse and validate the core OAuth 2.0 autz request params in 1664 // the context of OIDC 1665 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 1666 1667 Nonce nonce = Nonce.parse(MultivaluedMapUtils.getFirstValue(params, "nonce")); 1668 1669 if (! ar.specifiesRequestObject()) { 1670 1671 // Required params if no JAR is present 1672 1673 if (ar.getRedirectionURI() == null) { 1674 String msg = "Missing redirect_uri parameter"; 1675 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1676 ar.getClientID(), null, ar.impliedResponseMode(), ar.getState()); 1677 } 1678 1679 if (ar.getScope() == null) { 1680 String msg = "Missing scope parameter"; 1681 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1682 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1683 } 1684 1685 // Check nonce requirement 1686 if (nonce == null && Nonce.isRequired(ar.getResponseType())) { 1687 String msg = "Missing nonce parameter: Required for response_type=" + ar.getResponseType(); 1688 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1689 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1690 } 1691 } 1692 1693 // Check if present (not in JAR) 1694 if (ar.getResponseType() != null) { 1695 try { 1696 OIDCResponseTypeValidator.validate(ar.getResponseType()); 1697 } catch (IllegalArgumentException e) { 1698 String msg = "Unsupported response_type parameter: " + e.getMessage(); 1699 throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg), 1700 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1701 } 1702 } 1703 1704 // Check if present (not in JAR) 1705 if (ar.getScope() != null && ! ar.getScope().contains(OIDCScopeValue.OPENID)) { 1706 String msg = "The scope must include an openid value"; 1707 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1708 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1709 } 1710 1711 Display display = null; 1712 1713 if (params.containsKey("display")) { 1714 try { 1715 display = Display.parse(MultivaluedMapUtils.getFirstValue(params, "display")); 1716 1717 } catch (ParseException e) { 1718 String msg = "Invalid display parameter: " + e.getMessage(); 1719 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1720 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1721 } 1722 } 1723 1724 1725 String v = MultivaluedMapUtils.getFirstValue(params, "max_age"); 1726 1727 int maxAge = -1; 1728 1729 if (StringUtils.isNotBlank(v)) { 1730 1731 try { 1732 maxAge = Integer.parseInt(v); 1733 1734 } catch (NumberFormatException e) { 1735 String msg = "Invalid max_age parameter: " + v; 1736 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1737 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1738 } 1739 } 1740 1741 1742 v = MultivaluedMapUtils.getFirstValue(params, "ui_locales"); 1743 1744 List<LangTag> uiLocales = null; 1745 1746 if (StringUtils.isNotBlank(v)) { 1747 1748 uiLocales = new LinkedList<>(); 1749 1750 StringTokenizer st = new StringTokenizer(v, " "); 1751 1752 while (st.hasMoreTokens()) { 1753 1754 try { 1755 uiLocales.add(LangTag.parse(st.nextToken())); 1756 1757 } catch (LangTagException e) { 1758 String msg = "Invalid ui_locales parameter: " + e.getMessage(); 1759 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1760 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1761 } 1762 } 1763 } 1764 1765 1766 v = MultivaluedMapUtils.getFirstValue(params, "claims_locales"); 1767 1768 List<LangTag> claimsLocales = null; 1769 1770 if (StringUtils.isNotBlank(v)) { 1771 1772 claimsLocales = new LinkedList<>(); 1773 1774 StringTokenizer st = new StringTokenizer(v, " "); 1775 1776 while (st.hasMoreTokens()) { 1777 1778 try { 1779 claimsLocales.add(LangTag.parse(st.nextToken())); 1780 1781 } catch (LangTagException e) { 1782 String msg = "Invalid claims_locales parameter: " + e.getMessage(); 1783 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1784 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1785 } 1786 } 1787 } 1788 1789 1790 v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint"); 1791 1792 JWT idTokenHint = null; 1793 1794 if (StringUtils.isNotBlank(v)) { 1795 1796 try { 1797 idTokenHint = JWTParser.parse(v); 1798 1799 } catch (java.text.ParseException e) { 1800 String msg = "Invalid id_token_hint parameter: " + e.getMessage(); 1801 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1802 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1803 } 1804 } 1805 1806 String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint"); 1807 1808 1809 v = MultivaluedMapUtils.getFirstValue(params, "acr_values"); 1810 1811 List<ACR> acrValues = null; 1812 1813 if (StringUtils.isNotBlank(v)) { 1814 1815 acrValues = new LinkedList<>(); 1816 1817 StringTokenizer st = new StringTokenizer(v, " "); 1818 1819 while (st.hasMoreTokens()) { 1820 1821 acrValues.add(new ACR(st.nextToken())); 1822 } 1823 } 1824 1825 1826 v = MultivaluedMapUtils.getFirstValue(params, "claims"); 1827 1828 OIDCClaimsRequest claims = null; 1829 1830 if (StringUtils.isNotBlank(v)) { 1831 try { 1832 claims = OIDCClaimsRequest.parse(v); 1833 } catch (ParseException e) { 1834 String msg = "Invalid claims parameter: " + e.getMessage(); 1835 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1836 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1837 } 1838 } 1839 1840 String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose"); 1841 1842 if (purpose != null && (purpose.length() < PURPOSE_MIN_LENGTH || purpose.length() > PURPOSE_MAX_LENGTH)) { 1843 String msg = "Invalid purpose parameter: Must not be shorter than " + PURPOSE_MIN_LENGTH + " and longer than " + PURPOSE_MAX_LENGTH + " characters"; 1844 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1845 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1846 } 1847 1848 1849 // Parse additional custom parameters 1850 Map<String,List<String>> customParams = null; 1851 1852 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1853 1854 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1855 // We have a custom parameter 1856 if (customParams == null) { 1857 customParams = new HashMap<>(); 1858 } 1859 customParams.put(p.getKey(), p.getValue()); 1860 } 1861 } 1862 1863 1864 return new AuthenticationRequest( 1865 uri, ar.getResponseType(), ar.getResponseMode(), ar.getScope(), ar.getClientID(), ar.getRedirectionURI(), ar.getState(), nonce, 1866 display, ar.getPrompt(), maxAge, uiLocales, claimsLocales, 1867 idTokenHint, loginHint, acrValues, claims, purpose, 1868 ar.getRequestObject(), ar.getRequestURI(), 1869 ar.getCodeChallenge(), ar.getCodeChallengeMethod(), 1870 ar.getResources(), 1871 ar.includeGrantedScopes(), 1872 customParams); 1873 } 1874 1875 1876 /** 1877 * Parses an OpenID Connect authentication request from the specified 1878 * URI query string. 1879 * 1880 * <p>Example URI query string: 1881 * 1882 * <pre> 1883 * response_type=token%20id_token 1884 * &client_id=s6BhdRkqt3 1885 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1886 * &scope=openid%20profile 1887 * &state=af0ifjsldkj 1888 * &nonce=n-0S6_WzA2Mj 1889 * </pre> 1890 * 1891 * @param query The URI query string. Must not be {@code null}. 1892 * 1893 * @return The OpenID Connect authentication request. 1894 * 1895 * @throws ParseException If the query string couldn't be parsed to an 1896 * OpenID Connect authentication request. 1897 */ 1898 public static AuthenticationRequest parse(final String query) 1899 throws ParseException { 1900 1901 return parse(null, URLUtils.parseParameters(query)); 1902 } 1903 1904 1905 /** 1906 * Parses an OpenID Connect authentication request from the specified 1907 * URI query string. 1908 * 1909 * <p>Example URI query string: 1910 * 1911 * <pre> 1912 * response_type=token%20id_token 1913 * &client_id=s6BhdRkqt3 1914 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1915 * &scope=openid%20profile 1916 * &state=af0ifjsldkj 1917 * &nonce=n-0S6_WzA2Mj 1918 * </pre> 1919 * 1920 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May be 1921 * {@code null} if the {@link #toHTTPRequest} method will 1922 * not be used. 1923 * @param query The URI query string. Must not be {@code null}. 1924 * 1925 * @return The OpenID Connect authentication request. 1926 * 1927 * @throws ParseException If the query string couldn't be parsed to an 1928 * OpenID Connect authentication request. 1929 */ 1930 public static AuthenticationRequest parse(final URI uri, final String query) 1931 throws ParseException { 1932 1933 return parse(uri, URLUtils.parseParameters(query)); 1934 } 1935 1936 1937 /** 1938 * Parses an OpenID Connect authentication request from the specified 1939 * URI. 1940 * 1941 * <p>Example URI: 1942 * 1943 * <pre> 1944 * https://server.example.com/authorize? 1945 * response_type=token%20id_token 1946 * &client_id=s6BhdRkqt3 1947 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1948 * &scope=openid%20profile 1949 * &state=af0ifjsldkj 1950 * &nonce=n-0S6_WzA2Mj 1951 * </pre> 1952 * 1953 * @param uri The URI. Must not be {@code null}. 1954 * 1955 * @return The OpenID Connect authentication request. 1956 * 1957 * @throws ParseException If the query string couldn't be parsed to an 1958 * OpenID Connect authentication request. 1959 */ 1960 public static AuthenticationRequest parse(final URI uri) 1961 throws ParseException { 1962 1963 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1964 } 1965 1966 1967 /** 1968 * Parses an authentication request from the specified HTTP GET or HTTP 1969 * POST request. 1970 * 1971 * <p>Example HTTP request (GET): 1972 * 1973 * <pre> 1974 * https://server.example.com/op/authorize? 1975 * response_type=code%20id_token 1976 * &client_id=s6BhdRkqt3 1977 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1978 * &scope=openid 1979 * &nonce=n-0S6_WzA2Mj 1980 * &state=af0ifjsldkj 1981 * </pre> 1982 * 1983 * @param httpRequest The HTTP request. Must not be {@code null}. 1984 * 1985 * @return The OpenID Connect authentication request. 1986 * 1987 * @throws ParseException If the HTTP request couldn't be parsed to an 1988 * OpenID Connect authentication request. 1989 */ 1990 public static AuthenticationRequest parse(final HTTPRequest httpRequest) 1991 throws ParseException { 1992 1993 String query = httpRequest.getQuery(); 1994 1995 if (query == null) 1996 throw new ParseException("Missing URI query string"); 1997 1998 URI endpointURI = httpRequest.getURI(); 1999 2000 return parse(endpointURI, query); 2001 } 2002}