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