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