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 * &amp;client_id=s6BhdRkqt3
053 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
054 * &amp;scope=openid
055 * &amp;nonce=n-0S6_WzA2Mj
056 * &amp;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         * &amp;client_id=s6BhdRkqt3
1623         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1624         * &amp;scope=openid%20profile
1625         * &amp;state=af0ifjsldkj
1626         * &amp;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         * &amp;client_id=s6BhdRkqt3
1652         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1653         * &amp;scope=openid%20profile
1654         * &amp;state=af0ifjsldkj
1655         * &amp;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         * &amp;client_id=s6BhdRkqt3
1685         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1686         * &amp;scope=openid%20profile
1687         * &amp;state=af0ifjsldkj
1688         * &amp;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         * &amp;client_id=s6BhdRkqt3
1715         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1716         * &amp;scope=openid
1717         * &amp;nonce=n-0S6_WzA2Mj
1718         * &amp;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}