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