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