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