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