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