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