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 // Nonce required in the implicit and hybrid flows 1275 if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow())) 1276 throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow"); 1277 } 1278 1279 this.nonce = nonce; 1280 1281 // Optional parameters 1282 this.display = display; 1283 this.maxAge = maxAge; 1284 1285 if (uiLocales != null) 1286 this.uiLocales = Collections.unmodifiableList(uiLocales); 1287 else 1288 this.uiLocales = null; 1289 1290 if (claimsLocales != null) 1291 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 1292 else 1293 this.claimsLocales = null; 1294 1295 this.idTokenHint = idTokenHint; 1296 this.loginHint = loginHint; 1297 1298 if (acrValues != null) 1299 this.acrValues = Collections.unmodifiableList(acrValues); 1300 else 1301 this.acrValues = null; 1302 1303 this.claims = claims; 1304 1305 if (purpose != null) { 1306 if (purpose.length() < PURPOSE_MIN_LENGTH) { 1307 throw new IllegalArgumentException("The purpose must not be shorter than " + PURPOSE_MIN_LENGTH + " characters"); 1308 } 1309 if (purpose.length() > PURPOSE_MAX_LENGTH) { 1310 throw new IllegalArgumentException("The purpose must not be longer than " + PURPOSE_MAX_LENGTH +" characters"); 1311 } 1312 } 1313 1314 this.purpose = purpose; 1315 } 1316 1317 1318 /** 1319 * Returns the registered (standard) OpenID Connect authentication 1320 * request parameter names. 1321 * 1322 * @return The registered OpenID Connect authentication request 1323 * parameter names, as a unmodifiable set. 1324 */ 1325 public static Set<String> getRegisteredParameterNames() { 1326 1327 return REGISTERED_PARAMETER_NAMES; 1328 } 1329 1330 1331 /** 1332 * Gets the nonce. Corresponds to the conditionally optional 1333 * {@code nonce} parameter. 1334 * 1335 * @return The nonce, {@code null} if not specified. 1336 */ 1337 public Nonce getNonce() { 1338 1339 return nonce; 1340 } 1341 1342 1343 /** 1344 * Gets the requested display type. Corresponds to the optional 1345 * {@code display} parameter. 1346 * 1347 * @return The requested display type, {@code null} if not specified. 1348 */ 1349 public Display getDisplay() { 1350 1351 return display; 1352 } 1353 1354 1355 /** 1356 * Gets the required maximum authentication age. Corresponds to the 1357 * optional {@code max_age} parameter. 1358 * 1359 * @return The maximum authentication age, in seconds; -1 if not 1360 * specified, zero implies {@code prompt=login}. 1361 */ 1362 public int getMaxAge() { 1363 1364 return maxAge; 1365 } 1366 1367 1368 /** 1369 * Gets the end-user's preferred languages and scripts for the user 1370 * interface, ordered by preference. Corresponds to the optional 1371 * {@code ui_locales} parameter. 1372 * 1373 * @return The preferred UI locales, {@code null} if not specified. 1374 */ 1375 public List<LangTag> getUILocales() { 1376 1377 return uiLocales; 1378 } 1379 1380 1381 /** 1382 * Gets the end-user's preferred languages and scripts for the claims 1383 * being returned, ordered by preference. Corresponds to the optional 1384 * {@code claims_locales} parameter. 1385 * 1386 * @return The preferred claims locales, {@code null} if not specified. 1387 */ 1388 public List<LangTag> getClaimsLocales() { 1389 1390 return claimsLocales; 1391 } 1392 1393 1394 /** 1395 * Gets the ID Token hint. Corresponds to the conditionally optional 1396 * {@code id_token_hint} parameter. 1397 * 1398 * @return The ID Token hint, {@code null} if not specified. 1399 */ 1400 public JWT getIDTokenHint() { 1401 1402 return idTokenHint; 1403 } 1404 1405 1406 /** 1407 * Gets the login hint. Corresponds to the optional {@code login_hint} 1408 * parameter. 1409 * 1410 * @return The login hint, {@code null} if not specified. 1411 */ 1412 public String getLoginHint() { 1413 1414 return loginHint; 1415 } 1416 1417 1418 /** 1419 * Gets the requested Authentication Context Class Reference values. 1420 * Corresponds to the optional {@code acr_values} parameter. 1421 * 1422 * @return The requested ACR values, {@code null} if not specified. 1423 */ 1424 public List<ACR> getACRValues() { 1425 1426 return acrValues; 1427 } 1428 1429 1430 /** 1431 * Gets the individual claims to be returned. Corresponds to the 1432 * optional {@code claims} parameter. 1433 * 1434 * @see #getOIDCClaims() 1435 * 1436 * @return The individual claims to be returned, {@code null} if not 1437 * specified. 1438 */ 1439 @Deprecated 1440 public ClaimsRequest getClaims() { 1441 1442 return toClaimsRequestWithSilentFail(claims); 1443 } 1444 1445 1446 private static OIDCClaimsRequest toOIDCClaimsRequestWithSilentFail(final ClaimsRequest claims) { 1447 if (claims == null) { 1448 return null; 1449 } 1450 try { 1451 return OIDCClaimsRequest.parse(claims.toJSONObject()); 1452 } catch (ParseException e) { 1453 return null; 1454 } 1455 } 1456 1457 1458 private static ClaimsRequest toClaimsRequestWithSilentFail(final OIDCClaimsRequest claims) { 1459 if (claims == null) { 1460 return null; 1461 } 1462 try { 1463 return ClaimsRequest.parse(claims.toJSONObject()); 1464 } catch (ParseException e) { 1465 return null; 1466 } 1467 } 1468 1469 1470 /** 1471 * Gets the individual OpenID claims to be returned. Corresponds to the 1472 * optional {@code claims} parameter. 1473 * 1474 * @return The individual claims to be returned, {@code null} if not 1475 * specified. 1476 */ 1477 public OIDCClaimsRequest getOIDCClaims() { 1478 1479 return claims; 1480 } 1481 1482 1483 /** 1484 * Gets the transaction specific purpose. Corresponds to the optional 1485 * {@code purpose} parameter. 1486 * 1487 * @return The purpose, {@code null} if not specified. 1488 */ 1489 public String getPurpose() { 1490 1491 return purpose; 1492 } 1493 1494 1495 @Override 1496 public Map<String,List<String>> toParameters() { 1497 1498 Map <String,List<String>> params = super.toParameters(); 1499 1500 if (nonce != null) 1501 params.put("nonce", Collections.singletonList(nonce.toString())); 1502 1503 if (display != null) 1504 params.put("display", Collections.singletonList(display.toString())); 1505 1506 if (maxAge >= 0) 1507 params.put("max_age", Collections.singletonList("" + maxAge)); 1508 1509 if (uiLocales != null) { 1510 1511 StringBuilder sb = new StringBuilder(); 1512 1513 for (LangTag locale: uiLocales) { 1514 1515 if (sb.length() > 0) 1516 sb.append(' '); 1517 1518 sb.append(locale.toString()); 1519 } 1520 1521 params.put("ui_locales", Collections.singletonList(sb.toString())); 1522 } 1523 1524 if (claimsLocales != null) { 1525 1526 StringBuilder sb = new StringBuilder(); 1527 1528 for (LangTag locale: claimsLocales) { 1529 1530 if (sb.length() > 0) 1531 sb.append(' '); 1532 1533 sb.append(locale.toString()); 1534 } 1535 1536 params.put("claims_locales", Collections.singletonList(sb.toString())); 1537 } 1538 1539 if (idTokenHint != null) { 1540 1541 try { 1542 params.put("id_token_hint", Collections.singletonList(idTokenHint.serialize())); 1543 1544 } catch (IllegalStateException e) { 1545 1546 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 1547 } 1548 } 1549 1550 if (loginHint != null) 1551 params.put("login_hint", Collections.singletonList(loginHint)); 1552 1553 if (acrValues != null) { 1554 1555 StringBuilder sb = new StringBuilder(); 1556 1557 for (ACR acr: acrValues) { 1558 1559 if (sb.length() > 0) 1560 sb.append(' '); 1561 1562 sb.append(acr.toString()); 1563 } 1564 1565 params.put("acr_values", Collections.singletonList(sb.toString())); 1566 } 1567 1568 1569 if (claims != null) 1570 params.put("claims", Collections.singletonList(claims.toJSONObject().toString())); 1571 1572 if (purpose != null) 1573 params.put("purpose", Collections.singletonList(purpose)); 1574 1575 return params; 1576 } 1577 1578 1579 @Override 1580 public JWTClaimsSet toJWTClaimsSet() { 1581 1582 JWTClaimsSet jwtClaimsSet = super.toJWTClaimsSet(); 1583 1584 if (jwtClaimsSet.getClaim("max_age") != null) { 1585 // Convert max_age to number in JSON object 1586 try { 1587 String maxAgeString = jwtClaimsSet.getStringClaim("max_age"); 1588 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(jwtClaimsSet); 1589 builder.claim("max_age", Integer.parseInt(maxAgeString)); 1590 return builder.build(); 1591 } catch (java.text.ParseException e) { 1592 throw new SerializeException(e.getMessage()); 1593 } 1594 } 1595 1596 return jwtClaimsSet; 1597 } 1598 1599 1600 /** 1601 * Parses an OpenID Connect authentication request from the specified 1602 * URI query parameters. 1603 * 1604 * <p>Example parameters: 1605 * 1606 * <pre> 1607 * response_type = token id_token 1608 * client_id = s6BhdRkqt3 1609 * redirect_uri = https://client.example.com/cb 1610 * scope = openid profile 1611 * state = af0ifjsldkj 1612 * nonce = -0S6_WzA2Mj 1613 * </pre> 1614 * 1615 * @param params The parameters. Must not be {@code null}. 1616 * 1617 * @return The OpenID Connect authentication request. 1618 * 1619 * @throws ParseException If the parameters couldn't be parsed to an 1620 * OpenID Connect authentication request. 1621 */ 1622 public static AuthenticationRequest parse(final Map<String,List<String>> params) 1623 throws ParseException { 1624 1625 return parse(null, params); 1626 } 1627 1628 1629 /** 1630 * Parses an OpenID Connect authentication request from the specified 1631 * URI and query parameters. 1632 * 1633 * <p>Example parameters: 1634 * 1635 * <pre> 1636 * response_type = token id_token 1637 * client_id = s6BhdRkqt3 1638 * redirect_uri = https://client.example.com/cb 1639 * scope = openid profile 1640 * state = af0ifjsldkj 1641 * nonce = -0S6_WzA2Mj 1642 * </pre> 1643 * 1644 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May 1645 * be {@code null} if the {@link #toHTTPRequest} method 1646 * will not be used. 1647 * @param params The parameters. Must not be {@code null}. 1648 * 1649 * @return The OpenID Connect authentication request. 1650 * 1651 * @throws ParseException If the parameters couldn't be parsed to an 1652 * OpenID Connect authentication request. 1653 */ 1654 public static AuthenticationRequest parse(final URI uri, final Map<String,List<String>> params) 1655 throws ParseException { 1656 1657 // Parse and validate the core OAuth 2.0 autz request params in 1658 // the context of OIDC 1659 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 1660 1661 Nonce nonce = Nonce.parse(MultivaluedMapUtils.getFirstValue(params, "nonce")); 1662 1663 if (! ar.specifiesRequestObject()) { 1664 1665 // Required params if no JAR is present 1666 1667 if (ar.getRedirectionURI() == null) { 1668 String msg = "Missing redirect_uri parameter"; 1669 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1670 ar.getClientID(), null, ar.impliedResponseMode(), ar.getState()); 1671 } 1672 1673 if (ar.getScope() == null) { 1674 String msg = "Missing scope parameter"; 1675 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1676 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1677 } 1678 1679 // Nonce required in the implicit and hybrid flows 1680 if (nonce == null && (ar.getResponseType().impliesImplicitFlow() || ar.getResponseType().impliesHybridFlow())) { 1681 String msg = "Missing nonce parameter: Required in the implicit and hybrid flows"; 1682 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1683 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1684 } 1685 } 1686 1687 // Check if present (not in JAR) 1688 if (ar.getResponseType() != null) { 1689 try { 1690 OIDCResponseTypeValidator.validate(ar.getResponseType()); 1691 } catch (IllegalArgumentException e) { 1692 String msg = "Unsupported response_type parameter: " + e.getMessage(); 1693 throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg), 1694 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1695 } 1696 } 1697 1698 // Check if present (not in JAR) 1699 if (ar.getScope() != null && ! ar.getScope().contains(OIDCScopeValue.OPENID)) { 1700 String msg = "The scope must include an openid value"; 1701 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1702 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1703 } 1704 1705 Display display = null; 1706 1707 if (params.containsKey("display")) { 1708 try { 1709 display = Display.parse(MultivaluedMapUtils.getFirstValue(params, "display")); 1710 1711 } catch (ParseException e) { 1712 String msg = "Invalid display parameter: " + e.getMessage(); 1713 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1714 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1715 } 1716 } 1717 1718 1719 String v = MultivaluedMapUtils.getFirstValue(params, "max_age"); 1720 1721 int maxAge = -1; 1722 1723 if (StringUtils.isNotBlank(v)) { 1724 1725 try { 1726 maxAge = Integer.parseInt(v); 1727 1728 } catch (NumberFormatException e) { 1729 String msg = "Invalid max_age parameter: " + v; 1730 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1731 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1732 } 1733 } 1734 1735 1736 v = MultivaluedMapUtils.getFirstValue(params, "ui_locales"); 1737 1738 List<LangTag> uiLocales = null; 1739 1740 if (StringUtils.isNotBlank(v)) { 1741 1742 uiLocales = new LinkedList<>(); 1743 1744 StringTokenizer st = new StringTokenizer(v, " "); 1745 1746 while (st.hasMoreTokens()) { 1747 1748 try { 1749 uiLocales.add(LangTag.parse(st.nextToken())); 1750 1751 } catch (LangTagException e) { 1752 String msg = "Invalid ui_locales parameter: " + e.getMessage(); 1753 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1754 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1755 } 1756 } 1757 } 1758 1759 1760 v = MultivaluedMapUtils.getFirstValue(params, "claims_locales"); 1761 1762 List<LangTag> claimsLocales = null; 1763 1764 if (StringUtils.isNotBlank(v)) { 1765 1766 claimsLocales = new LinkedList<>(); 1767 1768 StringTokenizer st = new StringTokenizer(v, " "); 1769 1770 while (st.hasMoreTokens()) { 1771 1772 try { 1773 claimsLocales.add(LangTag.parse(st.nextToken())); 1774 1775 } catch (LangTagException e) { 1776 String msg = "Invalid claims_locales parameter: " + e.getMessage(); 1777 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1778 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1779 } 1780 } 1781 } 1782 1783 1784 v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint"); 1785 1786 JWT idTokenHint = null; 1787 1788 if (StringUtils.isNotBlank(v)) { 1789 1790 try { 1791 idTokenHint = JWTParser.parse(v); 1792 1793 } catch (java.text.ParseException e) { 1794 String msg = "Invalid id_token_hint 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 String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint"); 1801 1802 1803 v = MultivaluedMapUtils.getFirstValue(params, "acr_values"); 1804 1805 List<ACR> acrValues = null; 1806 1807 if (StringUtils.isNotBlank(v)) { 1808 1809 acrValues = new LinkedList<>(); 1810 1811 StringTokenizer st = new StringTokenizer(v, " "); 1812 1813 while (st.hasMoreTokens()) { 1814 1815 acrValues.add(new ACR(st.nextToken())); 1816 } 1817 } 1818 1819 1820 v = MultivaluedMapUtils.getFirstValue(params, "claims"); 1821 1822 OIDCClaimsRequest claims = null; 1823 1824 if (StringUtils.isNotBlank(v)) { 1825 try { 1826 claims = OIDCClaimsRequest.parse(v); 1827 } catch (ParseException e) { 1828 String msg = "Invalid claims parameter: " + e.getMessage(); 1829 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1830 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1831 } 1832 } 1833 1834 String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose"); 1835 1836 if (purpose != null && (purpose.length() < PURPOSE_MIN_LENGTH || purpose.length() > PURPOSE_MAX_LENGTH)) { 1837 String msg = "Invalid purpose parameter: Must not be shorter than " + PURPOSE_MIN_LENGTH + " and longer than " + PURPOSE_MAX_LENGTH + " characters"; 1838 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1839 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1840 } 1841 1842 1843 // Parse additional custom parameters 1844 Map<String,List<String>> customParams = null; 1845 1846 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1847 1848 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1849 // We have a custom parameter 1850 if (customParams == null) { 1851 customParams = new HashMap<>(); 1852 } 1853 customParams.put(p.getKey(), p.getValue()); 1854 } 1855 } 1856 1857 1858 return new AuthenticationRequest( 1859 uri, ar.getResponseType(), ar.getResponseMode(), ar.getScope(), ar.getClientID(), ar.getRedirectionURI(), ar.getState(), nonce, 1860 display, ar.getPrompt(), maxAge, uiLocales, claimsLocales, 1861 idTokenHint, loginHint, acrValues, claims, purpose, 1862 ar.getRequestObject(), ar.getRequestURI(), 1863 ar.getCodeChallenge(), ar.getCodeChallengeMethod(), 1864 ar.getResources(), 1865 ar.includeGrantedScopes(), 1866 customParams); 1867 } 1868 1869 1870 /** 1871 * Parses an OpenID Connect authentication request from the specified 1872 * URI query string. 1873 * 1874 * <p>Example URI query string: 1875 * 1876 * <pre> 1877 * response_type=token%20id_token 1878 * &client_id=s6BhdRkqt3 1879 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1880 * &scope=openid%20profile 1881 * &state=af0ifjsldkj 1882 * &nonce=n-0S6_WzA2Mj 1883 * </pre> 1884 * 1885 * @param query The URI query string. Must not be {@code null}. 1886 * 1887 * @return The OpenID Connect authentication request. 1888 * 1889 * @throws ParseException If the query string couldn't be parsed to an 1890 * OpenID Connect authentication request. 1891 */ 1892 public static AuthenticationRequest parse(final String query) 1893 throws ParseException { 1894 1895 return parse(null, URLUtils.parseParameters(query)); 1896 } 1897 1898 1899 /** 1900 * Parses an OpenID Connect authentication request from the specified 1901 * URI query string. 1902 * 1903 * <p>Example URI query string: 1904 * 1905 * <pre> 1906 * response_type=token%20id_token 1907 * &client_id=s6BhdRkqt3 1908 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1909 * &scope=openid%20profile 1910 * &state=af0ifjsldkj 1911 * &nonce=n-0S6_WzA2Mj 1912 * </pre> 1913 * 1914 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May be 1915 * {@code null} if the {@link #toHTTPRequest} method will 1916 * not be used. 1917 * @param query The URI query string. Must not be {@code null}. 1918 * 1919 * @return The OpenID Connect authentication request. 1920 * 1921 * @throws ParseException If the query string couldn't be parsed to an 1922 * OpenID Connect authentication request. 1923 */ 1924 public static AuthenticationRequest parse(final URI uri, final String query) 1925 throws ParseException { 1926 1927 return parse(uri, URLUtils.parseParameters(query)); 1928 } 1929 1930 1931 /** 1932 * Parses an OpenID Connect authentication request from the specified 1933 * URI. 1934 * 1935 * <p>Example URI: 1936 * 1937 * <pre> 1938 * https://server.example.com/authorize? 1939 * response_type=token%20id_token 1940 * &client_id=s6BhdRkqt3 1941 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1942 * &scope=openid%20profile 1943 * &state=af0ifjsldkj 1944 * &nonce=n-0S6_WzA2Mj 1945 * </pre> 1946 * 1947 * @param uri The URI. Must not be {@code null}. 1948 * 1949 * @return The OpenID Connect authentication request. 1950 * 1951 * @throws ParseException If the query string couldn't be parsed to an 1952 * OpenID Connect authentication request. 1953 */ 1954 public static AuthenticationRequest parse(final URI uri) 1955 throws ParseException { 1956 1957 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1958 } 1959 1960 1961 /** 1962 * Parses an authentication request from the specified HTTP GET or HTTP 1963 * POST request. 1964 * 1965 * <p>Example HTTP request (GET): 1966 * 1967 * <pre> 1968 * https://server.example.com/op/authorize? 1969 * response_type=code%20id_token 1970 * &client_id=s6BhdRkqt3 1971 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1972 * &scope=openid 1973 * &nonce=n-0S6_WzA2Mj 1974 * &state=af0ifjsldkj 1975 * </pre> 1976 * 1977 * @param httpRequest The HTTP request. Must not be {@code null}. 1978 * 1979 * @return The OpenID Connect authentication request. 1980 * 1981 * @throws ParseException If the HTTP request couldn't be parsed to an 1982 * OpenID Connect authentication request. 1983 */ 1984 public static AuthenticationRequest parse(final HTTPRequest httpRequest) 1985 throws ParseException { 1986 1987 String query = httpRequest.getQuery(); 1988 1989 if (query == null) 1990 throw new ParseException("Missing URI query string"); 1991 1992 URI endpointURI = httpRequest.getURI(); 1993 1994 return parse(endpointURI, query); 1995 } 1996}