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