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