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