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.html#ImplicitAuthRequest 1293 // https://openid.net/specs/openid-connect-core-1_0-27.html#HybridAuthRequest 1294 if (nonce == null && ( 1295 rt.equals(new ResponseType(ResponseType.Value.CODE, OIDCResponseTypeValue.ID_TOKEN)) || 1296 rt.equals(new ResponseType(OIDCResponseTypeValue.ID_TOKEN)) || 1297 rt.equals(new ResponseType(ResponseType.Value.CODE, OIDCResponseTypeValue.ID_TOKEN, ResponseType.Value.TOKEN)) 1298 ) 1299 ) { 1300 throw new IllegalArgumentException("Nonce required for response_type=" + rt); 1301 } 1302 } 1303 1304 this.nonce = nonce; 1305 1306 // Optional parameters 1307 this.display = display; 1308 this.maxAge = maxAge; 1309 1310 if (uiLocales != null) 1311 this.uiLocales = Collections.unmodifiableList(uiLocales); 1312 else 1313 this.uiLocales = null; 1314 1315 if (claimsLocales != null) 1316 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 1317 else 1318 this.claimsLocales = null; 1319 1320 this.idTokenHint = idTokenHint; 1321 this.loginHint = loginHint; 1322 1323 if (acrValues != null) 1324 this.acrValues = Collections.unmodifiableList(acrValues); 1325 else 1326 this.acrValues = null; 1327 1328 this.claims = claims; 1329 1330 if (purpose != null) { 1331 if (purpose.length() < PURPOSE_MIN_LENGTH) { 1332 throw new IllegalArgumentException("The purpose must not be shorter than " + PURPOSE_MIN_LENGTH + " characters"); 1333 } 1334 if (purpose.length() > PURPOSE_MAX_LENGTH) { 1335 throw new IllegalArgumentException("The purpose must not be longer than " + PURPOSE_MAX_LENGTH +" characters"); 1336 } 1337 } 1338 1339 this.purpose = purpose; 1340 } 1341 1342 1343 /** 1344 * Returns the registered (standard) OpenID Connect authentication 1345 * request parameter names. 1346 * 1347 * @return The registered OpenID Connect authentication request 1348 * parameter names, as a unmodifiable set. 1349 */ 1350 public static Set<String> getRegisteredParameterNames() { 1351 1352 return REGISTERED_PARAMETER_NAMES; 1353 } 1354 1355 1356 /** 1357 * Gets the nonce. Corresponds to the conditionally optional 1358 * {@code nonce} parameter. 1359 * 1360 * @return The nonce, {@code null} if not specified. 1361 */ 1362 public Nonce getNonce() { 1363 1364 return nonce; 1365 } 1366 1367 1368 /** 1369 * Gets the requested display type. Corresponds to the optional 1370 * {@code display} parameter. 1371 * 1372 * @return The requested display type, {@code null} if not specified. 1373 */ 1374 public Display getDisplay() { 1375 1376 return display; 1377 } 1378 1379 1380 /** 1381 * Gets the required maximum authentication age. Corresponds to the 1382 * optional {@code max_age} parameter. 1383 * 1384 * @return The maximum authentication age, in seconds; -1 if not 1385 * specified, zero implies {@code prompt=login}. 1386 */ 1387 public int getMaxAge() { 1388 1389 return maxAge; 1390 } 1391 1392 1393 /** 1394 * Gets the end-user's preferred languages and scripts for the user 1395 * interface, ordered by preference. Corresponds to the optional 1396 * {@code ui_locales} parameter. 1397 * 1398 * @return The preferred UI locales, {@code null} if not specified. 1399 */ 1400 public List<LangTag> getUILocales() { 1401 1402 return uiLocales; 1403 } 1404 1405 1406 /** 1407 * Gets the end-user's preferred languages and scripts for the claims 1408 * being returned, ordered by preference. Corresponds to the optional 1409 * {@code claims_locales} parameter. 1410 * 1411 * @return The preferred claims locales, {@code null} if not specified. 1412 */ 1413 public List<LangTag> getClaimsLocales() { 1414 1415 return claimsLocales; 1416 } 1417 1418 1419 /** 1420 * Gets the ID Token hint. Corresponds to the conditionally optional 1421 * {@code id_token_hint} parameter. 1422 * 1423 * @return The ID Token hint, {@code null} if not specified. 1424 */ 1425 public JWT getIDTokenHint() { 1426 1427 return idTokenHint; 1428 } 1429 1430 1431 /** 1432 * Gets the login hint. Corresponds to the optional {@code login_hint} 1433 * parameter. 1434 * 1435 * @return The login hint, {@code null} if not specified. 1436 */ 1437 public String getLoginHint() { 1438 1439 return loginHint; 1440 } 1441 1442 1443 /** 1444 * Gets the requested Authentication Context Class Reference values. 1445 * Corresponds to the optional {@code acr_values} parameter. 1446 * 1447 * @return The requested ACR values, {@code null} if not specified. 1448 */ 1449 public List<ACR> getACRValues() { 1450 1451 return acrValues; 1452 } 1453 1454 1455 /** 1456 * Gets the individual claims to be returned. Corresponds to the 1457 * optional {@code claims} parameter. 1458 * 1459 * @see #getOIDCClaims() 1460 * 1461 * @return The individual claims to be returned, {@code null} if not 1462 * specified. 1463 */ 1464 @Deprecated 1465 public ClaimsRequest getClaims() { 1466 1467 return toClaimsRequestWithSilentFail(claims); 1468 } 1469 1470 1471 private static OIDCClaimsRequest toOIDCClaimsRequestWithSilentFail(final ClaimsRequest claims) { 1472 if (claims == null) { 1473 return null; 1474 } 1475 try { 1476 return OIDCClaimsRequest.parse(claims.toJSONObject()); 1477 } catch (ParseException e) { 1478 return null; 1479 } 1480 } 1481 1482 1483 private static ClaimsRequest toClaimsRequestWithSilentFail(final OIDCClaimsRequest claims) { 1484 if (claims == null) { 1485 return null; 1486 } 1487 try { 1488 return ClaimsRequest.parse(claims.toJSONObject()); 1489 } catch (ParseException e) { 1490 return null; 1491 } 1492 } 1493 1494 1495 /** 1496 * Gets the individual OpenID claims to be returned. Corresponds to the 1497 * optional {@code claims} parameter. 1498 * 1499 * @return The individual claims to be returned, {@code null} if not 1500 * specified. 1501 */ 1502 public OIDCClaimsRequest getOIDCClaims() { 1503 1504 return claims; 1505 } 1506 1507 1508 /** 1509 * Gets the transaction specific purpose. Corresponds to the optional 1510 * {@code purpose} parameter. 1511 * 1512 * @return The purpose, {@code null} if not specified. 1513 */ 1514 public String getPurpose() { 1515 1516 return purpose; 1517 } 1518 1519 1520 @Override 1521 public Map<String,List<String>> toParameters() { 1522 1523 Map <String,List<String>> params = super.toParameters(); 1524 1525 if (nonce != null) 1526 params.put("nonce", Collections.singletonList(nonce.toString())); 1527 1528 if (display != null) 1529 params.put("display", Collections.singletonList(display.toString())); 1530 1531 if (maxAge >= 0) 1532 params.put("max_age", Collections.singletonList("" + maxAge)); 1533 1534 if (uiLocales != null) { 1535 1536 StringBuilder sb = new StringBuilder(); 1537 1538 for (LangTag locale: uiLocales) { 1539 1540 if (sb.length() > 0) 1541 sb.append(' '); 1542 1543 sb.append(locale.toString()); 1544 } 1545 1546 params.put("ui_locales", Collections.singletonList(sb.toString())); 1547 } 1548 1549 if (claimsLocales != null) { 1550 1551 StringBuilder sb = new StringBuilder(); 1552 1553 for (LangTag locale: claimsLocales) { 1554 1555 if (sb.length() > 0) 1556 sb.append(' '); 1557 1558 sb.append(locale.toString()); 1559 } 1560 1561 params.put("claims_locales", Collections.singletonList(sb.toString())); 1562 } 1563 1564 if (idTokenHint != null) { 1565 1566 try { 1567 params.put("id_token_hint", Collections.singletonList(idTokenHint.serialize())); 1568 1569 } catch (IllegalStateException e) { 1570 1571 throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e); 1572 } 1573 } 1574 1575 if (loginHint != null) 1576 params.put("login_hint", Collections.singletonList(loginHint)); 1577 1578 if (acrValues != null) { 1579 1580 StringBuilder sb = new StringBuilder(); 1581 1582 for (ACR acr: acrValues) { 1583 1584 if (sb.length() > 0) 1585 sb.append(' '); 1586 1587 sb.append(acr.toString()); 1588 } 1589 1590 params.put("acr_values", Collections.singletonList(sb.toString())); 1591 } 1592 1593 1594 if (claims != null) 1595 params.put("claims", Collections.singletonList(claims.toJSONObject().toString())); 1596 1597 if (purpose != null) 1598 params.put("purpose", Collections.singletonList(purpose)); 1599 1600 return params; 1601 } 1602 1603 1604 @Override 1605 public JWTClaimsSet toJWTClaimsSet() { 1606 1607 JWTClaimsSet jwtClaimsSet = super.toJWTClaimsSet(); 1608 1609 if (jwtClaimsSet.getClaim("max_age") != null) { 1610 // Convert max_age to number in JSON object 1611 try { 1612 String maxAgeString = jwtClaimsSet.getStringClaim("max_age"); 1613 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(jwtClaimsSet); 1614 builder.claim("max_age", Integer.parseInt(maxAgeString)); 1615 return builder.build(); 1616 } catch (java.text.ParseException e) { 1617 throw new SerializeException(e.getMessage()); 1618 } 1619 } 1620 1621 return jwtClaimsSet; 1622 } 1623 1624 1625 /** 1626 * Parses an OpenID Connect authentication request from the specified 1627 * URI query parameters. 1628 * 1629 * <p>Example parameters: 1630 * 1631 * <pre> 1632 * response_type = token id_token 1633 * client_id = s6BhdRkqt3 1634 * redirect_uri = https://client.example.com/cb 1635 * scope = openid profile 1636 * state = af0ifjsldkj 1637 * nonce = -0S6_WzA2Mj 1638 * </pre> 1639 * 1640 * @param params The parameters. Must not be {@code null}. 1641 * 1642 * @return The OpenID Connect authentication request. 1643 * 1644 * @throws ParseException If the parameters couldn't be parsed to an 1645 * OpenID Connect authentication request. 1646 */ 1647 public static AuthenticationRequest parse(final Map<String,List<String>> params) 1648 throws ParseException { 1649 1650 return parse(null, params); 1651 } 1652 1653 1654 /** 1655 * Parses an OpenID Connect authentication request from the specified 1656 * URI and query parameters. 1657 * 1658 * <p>Example parameters: 1659 * 1660 * <pre> 1661 * response_type = token id_token 1662 * client_id = s6BhdRkqt3 1663 * redirect_uri = https://client.example.com/cb 1664 * scope = openid profile 1665 * state = af0ifjsldkj 1666 * nonce = -0S6_WzA2Mj 1667 * </pre> 1668 * 1669 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May 1670 * be {@code null} if the {@link #toHTTPRequest} method 1671 * will not be used. 1672 * @param params The parameters. Must not be {@code null}. 1673 * 1674 * @return The OpenID Connect authentication request. 1675 * 1676 * @throws ParseException If the parameters couldn't be parsed to an 1677 * OpenID Connect authentication request. 1678 */ 1679 public static AuthenticationRequest parse(final URI uri, final Map<String,List<String>> params) 1680 throws ParseException { 1681 1682 // Parse and validate the core OAuth 2.0 autz request params in 1683 // the context of OIDC 1684 AuthorizationRequest ar = AuthorizationRequest.parse(uri, params); 1685 1686 Nonce nonce = Nonce.parse(MultivaluedMapUtils.getFirstValue(params, "nonce")); 1687 1688 if (! ar.specifiesRequestObject()) { 1689 1690 // Required params if no JAR is present 1691 1692 if (ar.getRedirectionURI() == null) { 1693 String msg = "Missing redirect_uri parameter"; 1694 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1695 ar.getClientID(), null, ar.impliedResponseMode(), ar.getState()); 1696 } 1697 1698 if (ar.getScope() == null) { 1699 String msg = "Missing scope parameter"; 1700 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1701 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1702 } 1703 1704 // Check nonce requirement 1705 // https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest 1706 // https://openid.net/specs/openid-connect-core-1_0-27.html#HybridAuthRequest 1707 if (nonce == null && ( 1708 ar.getResponseType().equals(new ResponseType(ResponseType.Value.CODE, OIDCResponseTypeValue.ID_TOKEN)) || 1709 ar.getResponseType().equals(new ResponseType(OIDCResponseTypeValue.ID_TOKEN)) || 1710 ar.getResponseType().equals(new ResponseType(ResponseType.Value.CODE, OIDCResponseTypeValue.ID_TOKEN, ResponseType.Value.TOKEN)) 1711 ) 1712 ) { 1713 String msg = "Missing nonce parameter: Required for response_type=" + ar.getResponseType(); 1714 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1715 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1716 } 1717 } 1718 1719 // Check if present (not in JAR) 1720 if (ar.getResponseType() != null) { 1721 try { 1722 OIDCResponseTypeValidator.validate(ar.getResponseType()); 1723 } catch (IllegalArgumentException e) { 1724 String msg = "Unsupported response_type parameter: " + e.getMessage(); 1725 throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg), 1726 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1727 } 1728 } 1729 1730 // Check if present (not in JAR) 1731 if (ar.getScope() != null && ! ar.getScope().contains(OIDCScopeValue.OPENID)) { 1732 String msg = "The scope must include an openid value"; 1733 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1734 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1735 } 1736 1737 Display display = null; 1738 1739 if (params.containsKey("display")) { 1740 try { 1741 display = Display.parse(MultivaluedMapUtils.getFirstValue(params, "display")); 1742 1743 } catch (ParseException e) { 1744 String msg = "Invalid display parameter: " + e.getMessage(); 1745 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1746 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1747 } 1748 } 1749 1750 1751 String v = MultivaluedMapUtils.getFirstValue(params, "max_age"); 1752 1753 int maxAge = -1; 1754 1755 if (StringUtils.isNotBlank(v)) { 1756 1757 try { 1758 maxAge = Integer.parseInt(v); 1759 1760 } catch (NumberFormatException e) { 1761 String msg = "Invalid max_age parameter: " + v; 1762 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1763 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1764 } 1765 } 1766 1767 1768 v = MultivaluedMapUtils.getFirstValue(params, "ui_locales"); 1769 1770 List<LangTag> uiLocales = null; 1771 1772 if (StringUtils.isNotBlank(v)) { 1773 1774 uiLocales = new LinkedList<>(); 1775 1776 StringTokenizer st = new StringTokenizer(v, " "); 1777 1778 while (st.hasMoreTokens()) { 1779 1780 try { 1781 uiLocales.add(LangTag.parse(st.nextToken())); 1782 1783 } catch (LangTagException e) { 1784 String msg = "Invalid ui_locales parameter: " + e.getMessage(); 1785 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1786 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1787 } 1788 } 1789 } 1790 1791 1792 v = MultivaluedMapUtils.getFirstValue(params, "claims_locales"); 1793 1794 List<LangTag> claimsLocales = null; 1795 1796 if (StringUtils.isNotBlank(v)) { 1797 1798 claimsLocales = new LinkedList<>(); 1799 1800 StringTokenizer st = new StringTokenizer(v, " "); 1801 1802 while (st.hasMoreTokens()) { 1803 1804 try { 1805 claimsLocales.add(LangTag.parse(st.nextToken())); 1806 1807 } catch (LangTagException e) { 1808 String msg = "Invalid claims_locales parameter: " + e.getMessage(); 1809 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1810 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1811 } 1812 } 1813 } 1814 1815 1816 v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint"); 1817 1818 JWT idTokenHint = null; 1819 1820 if (StringUtils.isNotBlank(v)) { 1821 1822 try { 1823 idTokenHint = JWTParser.parse(v); 1824 1825 } catch (java.text.ParseException e) { 1826 String msg = "Invalid id_token_hint parameter: " + e.getMessage(); 1827 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1828 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1829 } 1830 } 1831 1832 String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint"); 1833 1834 1835 v = MultivaluedMapUtils.getFirstValue(params, "acr_values"); 1836 1837 List<ACR> acrValues = null; 1838 1839 if (StringUtils.isNotBlank(v)) { 1840 1841 acrValues = new LinkedList<>(); 1842 1843 StringTokenizer st = new StringTokenizer(v, " "); 1844 1845 while (st.hasMoreTokens()) { 1846 1847 acrValues.add(new ACR(st.nextToken())); 1848 } 1849 } 1850 1851 1852 v = MultivaluedMapUtils.getFirstValue(params, "claims"); 1853 1854 OIDCClaimsRequest claims = null; 1855 1856 if (StringUtils.isNotBlank(v)) { 1857 try { 1858 claims = OIDCClaimsRequest.parse(v); 1859 } catch (ParseException e) { 1860 String msg = "Invalid claims parameter: " + e.getMessage(); 1861 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1862 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState(), e); 1863 } 1864 } 1865 1866 String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose"); 1867 1868 if (purpose != null && (purpose.length() < PURPOSE_MIN_LENGTH || purpose.length() > PURPOSE_MAX_LENGTH)) { 1869 String msg = "Invalid purpose parameter: Must not be shorter than " + PURPOSE_MIN_LENGTH + " and longer than " + PURPOSE_MAX_LENGTH + " characters"; 1870 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg), 1871 ar.getClientID(), ar.getRedirectionURI(), ar.impliedResponseMode(), ar.getState()); 1872 } 1873 1874 1875 // Parse additional custom parameters 1876 Map<String,List<String>> customParams = null; 1877 1878 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1879 1880 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1881 // We have a custom parameter 1882 if (customParams == null) { 1883 customParams = new HashMap<>(); 1884 } 1885 customParams.put(p.getKey(), p.getValue()); 1886 } 1887 } 1888 1889 1890 return new AuthenticationRequest( 1891 uri, ar.getResponseType(), ar.getResponseMode(), ar.getScope(), ar.getClientID(), ar.getRedirectionURI(), ar.getState(), nonce, 1892 display, ar.getPrompt(), maxAge, uiLocales, claimsLocales, 1893 idTokenHint, loginHint, acrValues, claims, purpose, 1894 ar.getRequestObject(), ar.getRequestURI(), 1895 ar.getCodeChallenge(), ar.getCodeChallengeMethod(), 1896 ar.getResources(), 1897 ar.includeGrantedScopes(), 1898 customParams); 1899 } 1900 1901 1902 /** 1903 * Parses an OpenID Connect authentication request from the specified 1904 * URI query string. 1905 * 1906 * <p>Example URI query string: 1907 * 1908 * <pre> 1909 * response_type=token%20id_token 1910 * &client_id=s6BhdRkqt3 1911 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1912 * &scope=openid%20profile 1913 * &state=af0ifjsldkj 1914 * &nonce=n-0S6_WzA2Mj 1915 * </pre> 1916 * 1917 * @param query The URI query string. Must not be {@code null}. 1918 * 1919 * @return The OpenID Connect authentication request. 1920 * 1921 * @throws ParseException If the query string couldn't be parsed to an 1922 * OpenID Connect authentication request. 1923 */ 1924 public static AuthenticationRequest parse(final String query) 1925 throws ParseException { 1926 1927 return parse(null, URLUtils.parseParameters(query)); 1928 } 1929 1930 1931 /** 1932 * Parses an OpenID Connect authentication request from the specified 1933 * URI query string. 1934 * 1935 * <p>Example URI query string: 1936 * 1937 * <pre> 1938 * response_type=token%20id_token 1939 * &client_id=s6BhdRkqt3 1940 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1941 * &scope=openid%20profile 1942 * &state=af0ifjsldkj 1943 * &nonce=n-0S6_WzA2Mj 1944 * </pre> 1945 * 1946 * @param uri The URI of the OAuth 2.0 authorisation endpoint. May be 1947 * {@code null} if the {@link #toHTTPRequest} method will 1948 * not be used. 1949 * @param query The URI query string. Must not be {@code null}. 1950 * 1951 * @return The OpenID Connect authentication request. 1952 * 1953 * @throws ParseException If the query string couldn't be parsed to an 1954 * OpenID Connect authentication request. 1955 */ 1956 public static AuthenticationRequest parse(final URI uri, final String query) 1957 throws ParseException { 1958 1959 return parse(uri, URLUtils.parseParameters(query)); 1960 } 1961 1962 1963 /** 1964 * Parses an OpenID Connect authentication request from the specified 1965 * URI. 1966 * 1967 * <p>Example URI: 1968 * 1969 * <pre> 1970 * https://server.example.com/authorize? 1971 * response_type=token%20id_token 1972 * &client_id=s6BhdRkqt3 1973 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 1974 * &scope=openid%20profile 1975 * &state=af0ifjsldkj 1976 * &nonce=n-0S6_WzA2Mj 1977 * </pre> 1978 * 1979 * @param uri The URI. Must not be {@code null}. 1980 * 1981 * @return The OpenID Connect authentication request. 1982 * 1983 * @throws ParseException If the query string couldn't be parsed to an 1984 * OpenID Connect authentication request. 1985 */ 1986 public static AuthenticationRequest parse(final URI uri) 1987 throws ParseException { 1988 1989 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 1990 } 1991 1992 1993 /** 1994 * Parses an authentication request from the specified HTTP GET or HTTP 1995 * POST request. 1996 * 1997 * <p>Example HTTP request (GET): 1998 * 1999 * <pre> 2000 * https://server.example.com/op/authorize? 2001 * response_type=code%20id_token 2002 * &client_id=s6BhdRkqt3 2003 * &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb 2004 * &scope=openid 2005 * &nonce=n-0S6_WzA2Mj 2006 * &state=af0ifjsldkj 2007 * </pre> 2008 * 2009 * @param httpRequest The HTTP request. Must not be {@code null}. 2010 * 2011 * @return The OpenID Connect authentication request. 2012 * 2013 * @throws ParseException If the HTTP request couldn't be parsed to an 2014 * OpenID Connect authentication request. 2015 */ 2016 public static AuthenticationRequest parse(final HTTPRequest httpRequest) 2017 throws ParseException { 2018 2019 String query = httpRequest.getQuery(); 2020 2021 if (query == null) 2022 throw new ParseException("Missing URI query string"); 2023 2024 URI endpointURI = httpRequest.getURI(); 2025 2026 return parse(endpointURI, query); 2027 } 2028}