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