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.StringUtils;
038import com.nimbusds.oauth2.sdk.util.URIUtils;
039import com.nimbusds.oauth2.sdk.util.URLUtils;
040import com.nimbusds.openid.connect.sdk.claims.ACR;
041import net.jcip.annotations.Immutable;
042import net.minidev.json.JSONObject;
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                 * @return This builder.
453                 */
454                public Builder nonce(final Nonce nonce) {
455
456                        this.nonce = nonce;
457                        return this;
458                }
459
460
461                /**
462                 * Sets the requested display type. Corresponds to the optional
463                 * {@code display} parameter.
464                 *
465                 * @param display The requested display type, {@code null} if
466                 *                not specified.
467                 *
468                 * @return This builder.
469                 */
470                public Builder display(final Display display) {
471
472                        this.display = display;
473                        return this;
474                }
475
476
477                /**
478                 * Sets the requested prompt. Corresponds to the optional
479                 * {@code prompt} parameter.
480                 *
481                 * @param prompt The requested prompt, {@code null} if not
482                 *               specified.
483                 *
484                 * @return This builder.
485                 */
486                public Builder prompt(final Prompt prompt) {
487
488                        this.prompt = prompt;
489                        return this;
490                }
491
492
493                /**
494                 * Sets the required maximum authentication age. Corresponds to
495                 * the optional {@code max_age} parameter.
496                 *
497                 * @param maxAge The maximum authentication age, in seconds; 0
498                 *               if not specified.
499                 *
500                 * @return This builder.
501                 */
502                public Builder maxAge(final int maxAge) {
503
504                        this.maxAge = maxAge;
505                        return this;
506                }
507
508
509                /**
510                 * Sets the end-user's preferred languages and scripts for the
511                 * user interface, ordered by preference. Corresponds to the
512                 * optional {@code ui_locales} parameter.
513                 *
514                 * @param uiLocales The preferred UI locales, {@code null} if
515                 *                  not specified.
516                 *
517                 * @return This builder.
518                 */
519                public Builder uiLocales(final List<LangTag> uiLocales) {
520
521                        this.uiLocales = uiLocales;
522                        return this;
523                }
524
525
526                /**
527                 * Sets the end-user's preferred languages and scripts for the
528                 * claims being returned, ordered by preference. Corresponds to
529                 * the optional {@code claims_locales} parameter.
530                 *
531                 * @param claimsLocales The preferred claims locales,
532                 *                      {@code null} if not specified.
533                 *
534                 * @return This builder.
535                 */
536                public Builder claimsLocales(final List<LangTag> claimsLocales) {
537
538                        this.claimsLocales = claimsLocales;
539                        return this;
540                }
541
542
543                /**
544                 * Sets the ID Token hint. Corresponds to the conditionally
545                 * optional {@code id_token_hint} parameter.
546                 *
547                 * @param idTokenHint The ID Token hint, {@code null} if not
548                 *                    specified.
549                 *
550                 * @return This builder.
551                 */
552                public Builder idTokenHint(final JWT idTokenHint) {
553
554                        this.idTokenHint = idTokenHint;
555                        return this;
556                }
557
558
559                /**
560                 * Sets the login hint. Corresponds to the optional
561                 * {@code login_hint} parameter.
562                 *
563                 * @param loginHint The login hint, {@code null} if not
564                 *                  specified.
565                 *
566                 * @return This builder.
567                 */
568                public Builder loginHint(final String loginHint) {
569
570                        this.loginHint = loginHint;
571                        return this;
572                }
573
574
575                /**
576                 * Sets the requested Authentication Context Class Reference
577                 * values. Corresponds to the optional {@code acr_values}
578                 * parameter.
579                 *
580                 * @param acrValues The requested ACR values, {@code null} if
581                 *                  not specified.
582                 *
583                 * @return This builder.
584                 */
585                public Builder acrValues(final List<ACR> acrValues) {
586
587                        this.acrValues = acrValues;
588                        return this;
589                }
590
591
592                /**
593                 * Sets the individual claims to be returned. Corresponds to
594                 * the optional {@code claims} parameter.
595                 *
596                 * @param claims The individual claims to be returned,
597                 *               {@code null} if not specified.
598                 *
599                 * @return This builder.
600                 */
601                public Builder claims(final ClaimsRequest claims) {
602
603                        this.claims = claims;
604                        return this;
605                }
606
607
608                /**
609                 * Sets the request object. Corresponds to the optional
610                 * {@code request} parameter. Must not be specified together
611                 * with a request object URI.
612                 *
613                 * @param requestObject The request object, {@code null} if not
614                 *                      specified.
615                 *
616                 * @return This builder.
617                 */
618                public Builder requestObject(final JWT requestObject) {
619
620                        this.requestObject = requestObject;
621                        return this;
622                }
623
624
625                /**
626                 * Sets the request object URI. Corresponds to the optional
627                 * {@code request_uri} parameter. Must not be specified
628                 * together with a request object.
629                 *
630                 * @param requestURI The request object URI, {@code null} if
631                 *                   not specified.
632                 *
633                 * @return This builder.
634                 */
635                public Builder requestURI(final URI requestURI) {
636
637                        this.requestURI = requestURI;
638                        return this;
639                }
640
641
642                /**
643                 * Sets the response mode. Corresponds to the optional
644                 * {@code response_mode} parameter. Use of this parameter is
645                 * not recommended unless a non-default response mode is
646                 * requested (e.g. form_post).
647                 *
648                 * @param rm The response mode, {@code null} if not specified.
649                 *
650                 * @return This builder.
651                 */
652                public Builder responseMode(final ResponseMode rm) {
653
654                        this.rm = rm;
655                        return this;
656                }
657                
658                
659                /**
660                 * Sets the code challenge for Proof Key for Code Exchange
661                 * (PKCE) by public OAuth clients.
662                 *
663                 * @param codeChallenge       The code challenge, {@code null}
664                 *                            if not specified.
665                 * @param codeChallengeMethod The code challenge method,
666                 *                            {@code null} if not specified.
667                 *
668                 * @return This builder.
669                 */
670                @Deprecated
671                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
672                        
673                        this.codeChallenge = codeChallenge;
674                        this.codeChallengeMethod = codeChallengeMethod;
675                        return this;
676                }
677                
678                
679                /**
680                 * Sets the code challenge for Proof Key for Code Exchange
681                 * (PKCE) by public OAuth clients.
682                 *
683                 * @param codeVerifier        The code verifier to use to
684                 *                            compute the code challenge,
685                 *                            {@code null} if PKCE is not
686                 *                            specified.
687                 * @param codeChallengeMethod The code challenge method,
688                 *                            {@code null} if not specified.
689                 *                            Defaults to
690                 *                            {@link CodeChallengeMethod#PLAIN}
691                 *                            if a code verifier is specified.
692                 *
693                 * @return This builder.
694                 */
695                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
696                        
697                        if (codeVerifier != null) {
698                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
699                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
700                                this.codeChallengeMethod = method;
701                        } else {
702                                this.codeChallenge = null;
703                                this.codeChallengeMethod = null;
704                        }
705                        return this;
706                }
707
708
709                /**
710                 * Sets the specified additional custom parameter.
711                 *
712                 * @param name  The parameter name. Must not be {@code null}.
713                 * @param value The parameter value, {@code null} if not
714                 *              specified.
715                 *
716                 * @return This builder.
717                 */
718                public Builder customParameter(final String name, final String value) {
719
720                        customParams.put(name, value);
721                        return this;
722                }
723
724
725                /**
726                 * Builds a new authentication request.
727                 *
728                 * @return The authentication request.
729                 */
730                public AuthenticationRequest build() {
731
732                        try {
733                                return new AuthenticationRequest(
734                                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
735                                        display, prompt, maxAge, uiLocales, claimsLocales,
736                                        idTokenHint, loginHint, acrValues, claims,
737                                        requestObject, requestURI,
738                                        codeChallenge, codeChallengeMethod,
739                                        customParams);
740
741                        } catch (IllegalArgumentException e) {
742
743                                throw new IllegalStateException(e.getMessage(), e);
744                        }
745                }
746        }
747        
748        
749        /**
750         * Creates a new minimal OpenID Connect authentication request.
751         *
752         * @param uri         The URI of the OAuth 2.0 authorisation endpoint.
753         *                    May be {@code null} if the {@link #toHTTPRequest}
754         *                    method will not be used.
755         * @param rt          The response type. Corresponds to the 
756         *                    {@code response_type} parameter. Must specify a
757         *                    valid OpenID Connect response type. Must not be
758         *                    {@code null}.
759         * @param scope       The request scope. Corresponds to the
760         *                    {@code scope} parameter. Must contain an
761         *                    {@link OIDCScopeValue#OPENID openid value}. Must
762         *                    not be {@code null}.
763         * @param clientID    The client identifier. Corresponds to the
764         *                    {@code client_id} parameter. Must not be 
765         *                    {@code null}.
766         * @param redirectURI The redirection URI. Corresponds to the
767         *                    {@code redirect_uri} parameter. Must not be 
768         *                    {@code null}.
769         * @param state       The state. Corresponds to the {@code state}
770         *                    parameter. May be {@code null}.
771         * @param nonce       The nonce. Corresponds to the {@code nonce} 
772         *                    parameter. May be {@code null} for code flow.
773         */
774        public AuthenticationRequest(final URI uri,
775                                     final ResponseType rt,
776                                     final Scope scope,
777                                     final ClientID clientID,
778                                     final URI redirectURI,
779                                     final State state,
780                                     final Nonce nonce) {
781
782                // Not specified: display, prompt, maxAge, uiLocales, claimsLocales, 
783                // idTokenHint, loginHint, acrValues, claims
784                // codeChallenge, codeChallengeMethod
785                this(uri, rt, null, scope, clientID, redirectURI, state, nonce,
786                     null, null, -1, null, null,
787                     null, null, null, null, null, null,
788                        null, null);
789        }
790        
791        
792        /**
793         * Creates a new OpenID Connect authentication request.
794         *
795         * @param uri                 The URI of the OAuth 2.0 authorisation
796         *                            endpoint. May be {@code null} if the
797         *                            {@link #toHTTPRequest} method will not be
798         *                            used.
799         * @param rt                  The response type set. Corresponds to the
800         *                            {@code response_type} parameter. Must
801         *                            specify a valid OpenID Connect response
802         *                            type. Must not be {@code null}.
803         * @param rm                  The response mode. Corresponds to the
804         *                            optional {@code response_mode} parameter.
805         *                            Use of this parameter is not recommended
806         *                            unless a non-default response mode is
807         *                            requested (e.g. form_post).
808         * @param scope               The request scope. Corresponds to the
809         *                            {@code scope} parameter. Must contain an
810         *                            {@link OIDCScopeValue#OPENID openid value}.
811         *                            Must not be {@code null}.
812         * @param clientID            The client identifier. Corresponds to the
813         *                            {@code client_id} parameter. Must not be
814         *                            {@code null}.
815         * @param redirectURI         The redirection URI. Corresponds to the
816         *                            {@code redirect_uri} parameter. Must not
817         *                            be {@code null} unless set by means of
818         *                            the optional {@code request_object} /
819         *                            {@code request_uri} parameter.
820         * @param state               The state. Corresponds to the recommended
821         *                            {@code state} parameter. {@code null} if
822         *                            not specified.
823         * @param nonce               The nonce. Corresponds to the
824         *                            {@code nonce} parameter. May be
825         *                            {@code null} for code flow.
826         * @param display             The requested display type. Corresponds
827         *                            to the optional {@code display}
828         *                            parameter.
829         *                            {@code null} if not specified.
830         * @param prompt              The requested prompt. Corresponds to the
831         *                            optional {@code prompt} parameter.
832         *                            {@code null} if not specified.
833         * @param maxAge              The required maximum authentication age,
834         *                            in seconds. Corresponds to the optional
835         *                            {@code max_age} parameter. Zero if not
836         *                            specified.
837         * @param uiLocales           The preferred languages and scripts for
838         *                            the user interface. Corresponds to the
839         *                            optional {@code ui_locales} parameter.
840         *                            {@code null} if not specified.
841         * @param claimsLocales       The preferred languages and scripts for
842         *                            claims being returned. Corresponds to the
843         *                            optional {@code claims_locales}
844         *                            parameter. {@code null} if not specified.
845         * @param idTokenHint         The ID Token hint. Corresponds to the
846         *                            optional {@code id_token_hint} parameter.
847         *                            {@code null} if not specified.
848         * @param loginHint           The login hint. Corresponds to the
849         *                            optional {@code login_hint} parameter.
850         *                            {@code null} if not specified.
851         * @param acrValues           The requested Authentication Context
852         *                            Class Reference values. Corresponds to
853         *                            the optional {@code acr_values}
854         *                            parameter. {@code null} if not specified.
855         * @param claims              The individual claims to be returned.
856         *                            Corresponds to the optional
857         *                            {@code claims} parameter. {@code null} if
858         *                            not specified.
859         * @param requestObject       The request object. Corresponds to the
860         *                            optional {@code request} parameter. Must
861         *                            not be specified together with a request
862         *                            object URI. {@code null} if not
863         *                            specified.
864         * @param requestURI          The request object URI. Corresponds to
865         *                            the optional {@code request_uri}
866         *                            parameter. Must not be specified together
867         *                            with a request object. {@code null} if
868         *                            not specified.
869         * @param codeChallenge       The code challenge for PKCE, {@code null}
870         *                            if not specified.
871         * @param codeChallengeMethod The code challenge method for PKCE,
872         *                            {@code null} if not specified.
873         */
874        public AuthenticationRequest(final URI uri,
875                                     final ResponseType rt,
876                                     final ResponseMode rm,
877                                     final Scope scope,
878                                     final ClientID clientID,
879                                     final URI redirectURI,
880                                     final State state,
881                                     final Nonce nonce,
882                                     final Display display,
883                                     final Prompt prompt,
884                                     final int maxAge,
885                                     final List<LangTag> uiLocales,
886                                     final List<LangTag> claimsLocales,
887                                     final JWT idTokenHint,
888                                     final String loginHint,
889                                     final List<ACR> acrValues,
890                                     final ClaimsRequest claims,
891                                     final JWT requestObject,
892                                     final URI requestURI,
893                                     final CodeChallenge codeChallenge,
894                                     final CodeChallengeMethod codeChallengeMethod) {
895
896                this(uri, rt, rm, scope, clientID, redirectURI, state,
897                        nonce, display, prompt, maxAge, uiLocales, claimsLocales,
898                        idTokenHint, loginHint, acrValues, claims,
899                        requestObject, requestURI, codeChallenge, codeChallengeMethod,
900                        Collections.<String, String>emptyMap());
901        }
902
903
904        /**
905         * Creates a new OpenID Connect authentication request with additional
906         * custom parameters.
907         *
908         * @param uri                 The URI of the OAuth 2.0 authorisation
909         *                            endpoint. May be {@code null} if the
910         *                            {@link #toHTTPRequest} method will not be
911         *                            used.
912         * @param rt                  The response type set. Corresponds to the
913         *                            {@code response_type} parameter. Must
914         *                            specify a valid OpenID Connect response
915         *                            type. Must not be {@code null}.
916         * @param rm                  The response mode. Corresponds to the
917         *                            optional {@code response_mode} parameter.
918         *                            Use of this parameter is not recommended
919         *                            unless a non-default response mode is
920         *                            requested (e.g. form_post).
921         * @param scope               The request scope. Corresponds to the
922         *                            {@code scope} parameter. Must contain an
923         *                            {@link OIDCScopeValue#OPENID openid value}.
924         *                            Must not be {@code null}.
925         * @param clientID            The client identifier. Corresponds to the
926         *                            {@code client_id} parameter. Must not be
927         *                            {@code null}.
928         * @param redirectURI         The redirection URI. Corresponds to the
929         *                            {@code redirect_uri} parameter. Must not
930         *                            be {@code null} unless set by means of
931         *                            the optional {@code request_object} /
932         *                            {@code request_uri} parameter.
933         * @param state               The state. Corresponds to the recommended
934         *                            {@code state} parameter. {@code null} if
935         *                            not specified.
936         * @param nonce               The nonce. Corresponds to the
937         *                            {@code nonce} parameter. May be
938         *                            {@code null} for code flow.
939         * @param display             The requested display type. Corresponds
940         *                            to the optional {@code display}
941         *                            parameter.
942         *                            {@code null} if not specified.
943         * @param prompt              The requested prompt. Corresponds to the
944         *                            optional {@code prompt} parameter.
945         *                            {@code null} if not specified.
946         * @param maxAge              The required maximum authentication age,
947         *                            in seconds. Corresponds to the optional
948         *                            {@code max_age} parameter. -1 if not
949         *                            specified, zero implies
950         *                            {@code prompt=login}.
951         * @param uiLocales           The preferred languages and scripts for
952         *                            the user interface. Corresponds to the
953         *                            optional {@code ui_locales} parameter.
954         *                            {@code null} if not specified.
955         * @param claimsLocales       The preferred languages and scripts for
956         *                            claims being returned. Corresponds to the
957         *                            optional {@code claims_locales}
958         *                            parameter. {@code null} if not specified.
959         * @param idTokenHint         The ID Token hint. Corresponds to the
960         *                            optional {@code id_token_hint} parameter.
961         *                            {@code null} if not specified.
962         * @param loginHint           The login hint. Corresponds to the
963         *                            optional {@code login_hint} parameter.
964         *                            {@code null} if not specified.
965         * @param acrValues           The requested Authentication Context
966         *                            Class Reference values. Corresponds to
967         *                            the optional {@code acr_values}
968         *                            parameter. {@code null} if not specified.
969         * @param claims              The individual claims to be returned.
970         *                            Corresponds to the optional
971         *                            {@code claims} parameter. {@code null} if
972         *                            not specified.
973         * @param requestObject       The request object. Corresponds to the
974         *                            optional {@code request} parameter. Must
975         *                            not be specified together with a request
976         *                            object URI. {@code null} if not
977         *                            specified.
978         * @param requestURI          The request object URI. Corresponds to
979         *                            the optional {@code request_uri}
980         *                            parameter. Must not be specified together
981         *                            with a request object. {@code null} if
982         *                            not specified.
983         * @param codeChallenge       The code challenge for PKCE, {@code null}
984         *                            if not specified.
985         * @param codeChallengeMethod The code challenge method for PKCE,
986         *                            {@code null} if not specified.
987         * @param customParams        Additional custom parameters, empty map
988         *                            or {@code null} if none.
989         */
990        public AuthenticationRequest(final URI uri,
991                                     final ResponseType rt,
992                                     final ResponseMode rm,
993                                     final Scope scope,
994                                     final ClientID clientID,
995                                     final URI redirectURI,
996                                     final State state,
997                                     final Nonce nonce,
998                                     final Display display,
999                                     final Prompt prompt,
1000                                     final int maxAge,
1001                                     final List<LangTag> uiLocales,
1002                                     final List<LangTag> claimsLocales,
1003                                     final JWT idTokenHint,
1004                                     final String loginHint,
1005                                     final List<ACR> acrValues,
1006                                     final ClaimsRequest claims,
1007                                     final JWT requestObject,
1008                                     final URI requestURI,
1009                                     final CodeChallenge codeChallenge,
1010                                     final CodeChallengeMethod codeChallengeMethod,
1011                                     final Map<String,String> customParams) {
1012
1013                super(uri, rt, rm, clientID, redirectURI, scope, state, codeChallenge, codeChallengeMethod, customParams);
1014
1015                // Redirect URI required unless set in request_object / request_uri
1016                if (redirectURI == null && requestObject == null && requestURI == null)
1017                        throw new IllegalArgumentException("The redirection URI must not be null");
1018                
1019                OIDCResponseTypeValidator.validate(rt);
1020
1021                if (scope == null)
1022                        throw new IllegalArgumentException("The scope must not be null");
1023
1024                if (! scope.contains(OIDCScopeValue.OPENID))
1025                        throw new IllegalArgumentException("The scope must include an \"openid\" token");
1026                
1027                
1028                // Nonce required in the implicit and hybrid flows
1029                if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow()))
1030                        throw new IllegalArgumentException("Nonce is required in implicit / hybrid protocol flow");
1031                
1032                this.nonce = nonce;
1033                
1034                // Optional parameters
1035                this.display = display;
1036                this.prompt = prompt;
1037                this.maxAge = maxAge;
1038
1039                if (uiLocales != null)
1040                        this.uiLocales = Collections.unmodifiableList(uiLocales);
1041                else
1042                        this.uiLocales = null;
1043
1044                if (claimsLocales != null)
1045                        this.claimsLocales = Collections.unmodifiableList(claimsLocales);
1046                else
1047                        this.claimsLocales = null;
1048
1049                this.idTokenHint = idTokenHint;
1050                this.loginHint = loginHint;
1051
1052                if (acrValues != null)
1053                        this.acrValues = Collections.unmodifiableList(acrValues);
1054                else
1055                        this.acrValues = null;
1056
1057                this.claims = claims;
1058
1059                if (requestObject != null && requestURI != null)
1060                        throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both");
1061
1062                this.requestObject = requestObject;
1063                this.requestURI = requestURI;
1064        }
1065
1066
1067        /**
1068         * Returns the registered (standard) OpenID Connect authentication
1069         * request parameter names.
1070         *
1071         * @return The registered OpenID Connect authentication request
1072         *         parameter names, as a unmodifiable set.
1073         */
1074        public static Set<String> getRegisteredParameterNames() {
1075
1076                return REGISTERED_PARAMETER_NAMES;
1077        }
1078        
1079        
1080        /**
1081         * Gets the nonce. Corresponds to the conditionally optional 
1082         * {@code nonce} parameter.
1083         *
1084         * @return The nonce, {@code null} if not specified.
1085         */
1086        public Nonce getNonce() {
1087        
1088                return nonce;
1089        }
1090        
1091        
1092        /**
1093         * Gets the requested display type. Corresponds to the optional
1094         * {@code display} parameter.
1095         *
1096         * @return The requested display type, {@code null} if not specified.
1097         */
1098        public Display getDisplay() {
1099        
1100                return display;
1101        }
1102        
1103        
1104        /**
1105         * Gets the requested prompt. Corresponds to the optional 
1106         * {@code prompt} parameter.
1107         *
1108         * @return The requested prompt, {@code null} if not specified.
1109         */
1110        public Prompt getPrompt() {
1111        
1112                return prompt;
1113        }
1114
1115
1116        /**
1117         * Gets the required maximum authentication age. Corresponds to the
1118         * optional {@code max_age} parameter.
1119         *
1120         * @return The maximum authentication age, in seconds; -1 if not
1121         *         specified, zero implies {@code prompt=login}.
1122         */
1123        public int getMaxAge() {
1124        
1125                return maxAge;
1126        }
1127
1128
1129        /**
1130         * Gets the end-user's preferred languages and scripts for the user
1131         * interface, ordered by preference. Corresponds to the optional
1132         * {@code ui_locales} parameter.
1133         *
1134         * @return The preferred UI locales, {@code null} if not specified.
1135         */
1136        public List<LangTag> getUILocales() {
1137
1138                return uiLocales;
1139        }
1140
1141
1142        /**
1143         * Gets the end-user's preferred languages and scripts for the claims
1144         * being returned, ordered by preference. Corresponds to the optional
1145         * {@code claims_locales} parameter.
1146         *
1147         * @return The preferred claims locales, {@code null} if not specified.
1148         */
1149        public List<LangTag> getClaimsLocales() {
1150
1151                return claimsLocales;
1152        }
1153
1154
1155        /**
1156         * Gets the ID Token hint. Corresponds to the conditionally optional 
1157         * {@code id_token_hint} parameter.
1158         *
1159         * @return The ID Token hint, {@code null} if not specified.
1160         */
1161        public JWT getIDTokenHint() {
1162        
1163                return idTokenHint;
1164        }
1165
1166
1167        /**
1168         * Gets the login hint. Corresponds to the optional {@code login_hint} 
1169         * parameter.
1170         *
1171         * @return The login hint, {@code null} if not specified.
1172         */
1173        public String getLoginHint() {
1174
1175                return loginHint;
1176        }
1177
1178
1179        /**
1180         * Gets the requested Authentication Context Class Reference values.
1181         * Corresponds to the optional {@code acr_values} parameter.
1182         *
1183         * @return The requested ACR values, {@code null} if not specified.
1184         */
1185        public List<ACR> getACRValues() {
1186
1187                return acrValues;
1188        }
1189
1190
1191        /**
1192         * Gets the individual claims to be returned. Corresponds to the 
1193         * optional {@code claims} parameter.
1194         *
1195         * @return The individual claims to be returned, {@code null} if not
1196         *         specified.
1197         */
1198        public ClaimsRequest getClaims() {
1199
1200                return claims;
1201        }
1202        
1203        
1204        /**
1205         * Gets the request object. Corresponds to the optional {@code request} 
1206         * parameter.
1207         *
1208         * @return The request object, {@code null} if not specified.
1209         */
1210        public JWT getRequestObject() {
1211        
1212                return requestObject;
1213        }
1214        
1215        
1216        /**
1217         * Gets the request object URI. Corresponds to the optional
1218         * {@code request_uri} parameter.
1219         *
1220         * @return The request object URI, {@code null} if not specified.
1221         */
1222        public URI getRequestURI() {
1223        
1224                return requestURI;
1225        }
1226        
1227        
1228        /**
1229         * Returns {@code true} if this authentication request specifies an
1230         * OpenID Connect request object (directly through the {@code request} 
1231         * parameter or by reference through the {@code request_uri} parameter).
1232         *
1233         * @return {@code true} if a request object is specified, else 
1234         *         {@code false}.
1235         */
1236        public boolean specifiesRequestObject() {
1237        
1238                return requestObject != null || requestURI != null;
1239        }
1240
1241
1242        @Override
1243        public Map<String,String> toParameters() {
1244
1245                Map <String,String> params = super.toParameters();
1246                
1247                if (nonce != null)
1248                        params.put("nonce", nonce.toString());
1249                
1250                if (display != null)
1251                        params.put("display", display.toString());
1252                
1253                if (prompt != null)
1254                        params.put("prompt", prompt.toString());
1255
1256                if (maxAge >= 0)
1257                        params.put("max_age", "" + maxAge);
1258
1259                if (uiLocales != null) {
1260
1261                        StringBuilder sb = new StringBuilder();
1262
1263                        for (LangTag locale: uiLocales) {
1264
1265                                if (sb.length() > 0)
1266                                        sb.append(' ');
1267
1268                                sb.append(locale.toString());
1269                        }
1270
1271                        params.put("ui_locales", sb.toString());
1272                }
1273
1274                if (claimsLocales != null) {
1275
1276                        StringBuilder sb = new StringBuilder();
1277
1278                        for (LangTag locale: claimsLocales) {
1279
1280                                if (sb.length() > 0)
1281                                        sb.append(' ');
1282
1283                                sb.append(locale.toString());
1284                        }
1285
1286                        params.put("claims_locales", sb.toString());
1287                }
1288
1289                if (idTokenHint != null) {
1290                
1291                        try {
1292                                params.put("id_token_hint", idTokenHint.serialize());
1293                                
1294                        } catch (IllegalStateException e) {
1295                        
1296                                throw new SerializeException("Couldn't serialize ID token hint: " + e.getMessage(), e);
1297                        }
1298                }
1299
1300                if (loginHint != null)
1301                        params.put("login_hint", loginHint);
1302
1303                if (acrValues != null) {
1304
1305                        StringBuilder sb = new StringBuilder();
1306
1307                        for (ACR acr: acrValues) {
1308
1309                                if (sb.length() > 0)
1310                                        sb.append(' ');
1311
1312                                sb.append(acr.toString());
1313                        }
1314
1315                        params.put("acr_values", sb.toString());
1316                }
1317                        
1318
1319                if (claims != null)
1320                        params.put("claims", claims.toJSONObject().toString());
1321                
1322                if (requestObject != null) {
1323                
1324                        try {
1325                                params.put("request", requestObject.serialize());
1326                                
1327                        } catch (IllegalStateException e) {
1328                        
1329                                throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
1330                        }
1331                }
1332                
1333                if (requestURI != null)
1334                        params.put("request_uri", requestURI.toString());
1335
1336                return params;
1337        }
1338
1339
1340        /**
1341         * Parses an OpenID Connect authentication request from the specified
1342         * parameters.
1343         *
1344         * <p>Example parameters:
1345         *
1346         * <pre>
1347         * response_type = token id_token
1348         * client_id     = s6BhdRkqt3
1349         * redirect_uri  = https://client.example.com/cb
1350         * scope         = openid profile
1351         * state         = af0ifjsldkj
1352         * nonce         = -0S6_WzA2Mj
1353         * </pre>
1354         *
1355         * @param params The parameters. Must not be {@code null}.
1356         *
1357         * @return The OpenID Connect authentication request.
1358         *
1359         * @throws ParseException If the parameters couldn't be parsed to an
1360         *                        OpenID Connect authentication request.
1361         */
1362        public static AuthenticationRequest parse(final Map<String,String> params)
1363                throws ParseException {
1364
1365                return parse(null, params);
1366        }
1367
1368
1369        /**
1370         * Parses an OpenID Connect authentication request from the specified
1371         * parameters.
1372         *
1373         * <p>Example parameters:
1374         *
1375         * <pre>
1376         * response_type = token id_token
1377         * client_id     = s6BhdRkqt3
1378         * redirect_uri  = https://client.example.com/cb
1379         * scope         = openid profile
1380         * state         = af0ifjsldkj
1381         * nonce         = -0S6_WzA2Mj
1382         * </pre>
1383         *
1384         * @param uri    The URI of the OAuth 2.0 authorisation endpoint. May
1385         *               be {@code null} if the {@link #toHTTPRequest} method
1386         *               will not be used.
1387         * @param params The parameters. Must not be {@code null}.
1388         *
1389         * @return The OpenID Connect authentication request.
1390         *
1391         * @throws ParseException If the parameters couldn't be parsed to an
1392         *                        OpenID Connect authentication request.
1393         */
1394        public static AuthenticationRequest parse(final URI uri, final Map<String,String> params)
1395                throws ParseException {
1396
1397                // Parse and validate the core OAuth 2.0 autz request params in 
1398                // the context of OIDC
1399                AuthorizationRequest ar = AuthorizationRequest.parse(uri, params);
1400
1401                ClientID clientID = ar.getClientID();
1402                State state = ar.getState();
1403                ResponseMode rm = ar.getResponseMode();
1404
1405                // Required in OIDC, check later after optional request_object / request_uri is parsed
1406                URI redirectURI = ar.getRedirectionURI();
1407
1408                ResponseType rt = ar.getResponseType();
1409                
1410                try {
1411                        OIDCResponseTypeValidator.validate(rt);
1412                        
1413                } catch (IllegalArgumentException e) {
1414                        String msg = "Unsupported \"response_type\" parameter: " + e.getMessage();
1415                        throw new ParseException(msg, OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.appendDescription(": " + msg),
1416                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1417                }
1418                
1419                // Required in OIDC, must include "openid" parameter
1420                Scope scope = ar.getScope();
1421
1422                if (scope == null) {
1423                        String msg = "Missing \"scope\" parameter";
1424                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1425                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1426                }
1427
1428                if (! scope.contains(OIDCScopeValue.OPENID)) {
1429                        String msg = "The scope must include an \"openid\" value";
1430                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1431                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1432                }
1433
1434
1435                // Parse the remaining OIDC parameters
1436                Nonce nonce = Nonce.parse(params.get("nonce"));
1437                
1438                // Nonce required in the implicit and hybrid flows
1439                if (nonce == null && (rt.impliesImplicitFlow() || rt.impliesHybridFlow())) {
1440                        String msg = "Missing \"nonce\" parameter: Required in the implicit and hybrid flows";
1441                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1442                                                 clientID, redirectURI, ar.impliedResponseMode(), state);
1443                }
1444                
1445                Display display = null;
1446
1447                if (params.containsKey("display")) {
1448                        try {
1449                                display = Display.parse(params.get("display"));
1450
1451                        } catch (ParseException e) {
1452                                String msg = "Invalid \"display\" parameter: " + e.getMessage();
1453                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1454                                        clientID, redirectURI, ar.impliedResponseMode(), state, e);
1455                        }
1456                }
1457                
1458                
1459                Prompt prompt;
1460                
1461                try {
1462                        prompt = Prompt.parse(params.get("prompt"));
1463                                
1464                } catch (ParseException e) {
1465                        String msg = "Invalid \"prompt\" parameter: " + e.getMessage();
1466                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1467                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1468                }
1469
1470
1471                String v = params.get("max_age");
1472
1473                int maxAge = -1;
1474
1475                if (StringUtils.isNotBlank(v)) {
1476
1477                        try {
1478                                maxAge = Integer.parseInt(v);
1479
1480                        } catch (NumberFormatException e) {
1481                                String msg = "Invalid \"max_age\" parameter: " + v;
1482                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1483                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1484                        }
1485                }
1486
1487
1488                v = params.get("ui_locales");
1489
1490                List<LangTag> uiLocales = null;
1491
1492                if (StringUtils.isNotBlank(v)) {
1493
1494                        uiLocales = new LinkedList<>();
1495
1496                        StringTokenizer st = new StringTokenizer(v, " ");
1497
1498                        while (st.hasMoreTokens()) {
1499
1500                                try {
1501                                        uiLocales.add(LangTag.parse(st.nextToken()));
1502
1503                                } catch (LangTagException e) {
1504                                        String msg = "Invalid \"ui_locales\" parameter: " + e.getMessage();
1505                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1506                                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1507                                }
1508                        }
1509                }
1510
1511
1512                v = params.get("claims_locales");
1513
1514                List<LangTag> claimsLocales = null;
1515
1516                if (StringUtils.isNotBlank(v)) {
1517
1518                        claimsLocales = new LinkedList<>();
1519
1520                        StringTokenizer st = new StringTokenizer(v, " ");
1521
1522                        while (st.hasMoreTokens()) {
1523
1524                                try {
1525                                        claimsLocales.add(LangTag.parse(st.nextToken()));
1526
1527                                } catch (LangTagException e) {
1528                                        String msg = "Invalid \"claims_locales\" parameter: " + e.getMessage();
1529                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1530                                                                 clientID, redirectURI, ar.impliedResponseMode(), state, e);
1531                                }
1532                        }
1533                }
1534
1535
1536                v = params.get("id_token_hint");
1537                
1538                JWT idTokenHint = null;
1539                
1540                if (StringUtils.isNotBlank(v)) {
1541                
1542                        try {
1543                                idTokenHint = JWTParser.parse(v);
1544                                
1545                        } catch (java.text.ParseException e) {
1546                                String msg = "Invalid \"id_token_hint\" parameter: " + e.getMessage();
1547                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1548                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1549                        }
1550                }
1551
1552                String loginHint = params.get("login_hint");
1553
1554
1555                v = params.get("acr_values");
1556
1557                List<ACR> acrValues = null;
1558
1559                if (StringUtils.isNotBlank(v)) {
1560
1561                        acrValues = new LinkedList<>();
1562
1563                        StringTokenizer st = new StringTokenizer(v, " ");
1564
1565                        while (st.hasMoreTokens()) {
1566
1567                                acrValues.add(new ACR(st.nextToken()));
1568                        }
1569                }
1570
1571
1572                v = params.get("claims");
1573
1574                ClaimsRequest claims = null;
1575
1576                if (StringUtils.isNotBlank(v)) {
1577
1578                        JSONObject jsonObject;
1579
1580                        try {
1581                                jsonObject = JSONObjectUtils.parse(v);
1582
1583                        } catch (ParseException e) {
1584                                String msg = "Invalid \"claims\" parameter: " + e.getMessage();
1585                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1586                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1587                        }
1588
1589                        // Parse exceptions silently ignored
1590                        claims = ClaimsRequest.parse(jsonObject);
1591                }
1592                
1593                
1594                v = params.get("request_uri");
1595                
1596                URI requestURI = null;
1597                
1598                if (StringUtils.isNotBlank(v)) {
1599
1600                        try {
1601                                requestURI = new URI(v);
1602                
1603                        } catch (URISyntaxException e) {
1604                                String msg = "Invalid \"request_uri\" parameter: " + e.getMessage();
1605                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1606                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1607                        }
1608                }
1609
1610                v = params.get("request");
1611
1612                JWT requestObject = null;
1613
1614                if (StringUtils.isNotBlank(v)) {
1615
1616                        // request_object and request_uri must not be defined at the same time
1617                        if (requestURI != null) {
1618                                String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters";
1619                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1620                                                         clientID, redirectURI, ar.impliedResponseMode(), state, null);
1621                        }
1622
1623                        try {
1624                                requestObject = JWTParser.parse(v);
1625                                
1626                        } catch (java.text.ParseException e) {
1627                                String msg = "Invalid \"request_object\" parameter: " + e.getMessage();
1628                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1629                                                         clientID, redirectURI, ar.impliedResponseMode(), state, e);
1630                        }
1631                }
1632
1633
1634                // Redirect URI required unless request_object / request_uri present
1635                if (redirectURI == null && requestObject == null && requestURI == null) {
1636                        String msg = "Missing \"redirect_uri\" parameter";
1637                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1638                                clientID, null, ar.impliedResponseMode(), state);
1639                }
1640
1641                // Parse additional custom parameters
1642                Map<String,String> customParams = null;
1643
1644                for (Map.Entry<String,String> p: params.entrySet()) {
1645
1646                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1647                                // We have a custom parameter
1648                                if (customParams == null) {
1649                                        customParams = new HashMap<>();
1650                                }
1651                                customParams.put(p.getKey(), p.getValue());
1652                        }
1653                }
1654
1655
1656                return new AuthenticationRequest(
1657                        uri, rt, rm, scope, clientID, redirectURI, state, nonce,
1658                        display, prompt, maxAge, uiLocales, claimsLocales,
1659                        idTokenHint, loginHint, acrValues, claims, requestObject, requestURI,
1660                        ar.getCodeChallenge(), ar.getCodeChallengeMethod(),
1661                        customParams);
1662        }
1663        
1664        
1665        /**
1666         * Parses an OpenID Connect authentication request from the specified
1667         * URI query string.
1668         *
1669         * <p>Example URI query string:
1670         *
1671         * <pre>
1672         * response_type=token%20id_token
1673         * &amp;client_id=s6BhdRkqt3
1674         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1675         * &amp;scope=openid%20profile
1676         * &amp;state=af0ifjsldkj
1677         * &amp;nonce=n-0S6_WzA2Mj
1678         * </pre>
1679         *
1680         * @param query The URI query string. Must not be {@code null}.
1681         *
1682         * @return The OpenID Connect authentication request.
1683         *
1684         * @throws ParseException If the query string couldn't be parsed to an 
1685         *                        OpenID Connect authentication request.
1686         */
1687        public static AuthenticationRequest parse(final String query)
1688                throws ParseException {
1689        
1690                return parse(null, URLUtils.parseParameters(query));
1691        }
1692
1693
1694        /**
1695         * Parses an OpenID Connect authentication request from the specified
1696         * URI query string.
1697         *
1698         * <p>Example URI query string:
1699         *
1700         * <pre>
1701         * response_type=token%20id_token
1702         * &amp;client_id=s6BhdRkqt3
1703         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1704         * &amp;scope=openid%20profile
1705         * &amp;state=af0ifjsldkj
1706         * &amp;nonce=n-0S6_WzA2Mj
1707         * </pre>
1708         *
1709         * @param uri   The URI of the OAuth 2.0 authorisation endpoint. May be
1710         *              {@code null} if the {@link #toHTTPRequest} method will
1711         *              not be used.
1712         * @param query The URI query string. Must not be {@code null}.
1713         *
1714         * @return The OpenID Connect authentication request.
1715         *
1716         * @throws ParseException If the query string couldn't be parsed to an
1717         *                        OpenID Connect authentication request.
1718         */
1719        public static AuthenticationRequest parse(final URI uri, final String query)
1720                throws ParseException {
1721
1722                return parse(uri, URLUtils.parseParameters(query));
1723        }
1724
1725
1726        /**
1727         * Parses an OpenID Connect authentication request from the specified
1728         * URI.
1729         *
1730         * <p>Example URI:
1731         *
1732         * <pre>
1733         * https://server.example.com/authorize?
1734         * response_type=token%20id_token
1735         * &amp;client_id=s6BhdRkqt3
1736         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1737         * &amp;scope=openid%20profile
1738         * &amp;state=af0ifjsldkj
1739         * &amp;nonce=n-0S6_WzA2Mj
1740         * </pre>
1741         *
1742         * @param uri The URI. Must not be {@code null}.
1743         *
1744         * @return The OpenID Connect authentication request.
1745         *
1746         * @throws ParseException If the query string couldn't be parsed to an
1747         *                        OpenID Connect authentication request.
1748         */
1749        public static AuthenticationRequest parse(final URI uri)
1750                throws ParseException {
1751
1752                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1753        }
1754        
1755        
1756        /**
1757         * Parses an authentication request from the specified HTTP GET or HTTP
1758         * POST request.
1759         *
1760         * <p>Example HTTP request (GET):
1761         *
1762         * <pre>
1763         * https://server.example.com/op/authorize?
1764         * response_type=code%20id_token
1765         * &amp;client_id=s6BhdRkqt3
1766         * &amp;redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
1767         * &amp;scope=openid
1768         * &amp;nonce=n-0S6_WzA2Mj
1769         * &amp;state=af0ifjsldkj
1770         * </pre>
1771         *
1772         * @param httpRequest The HTTP request. Must not be {@code null}.
1773         *
1774         * @return The OpenID Connect authentication request.
1775         *
1776         * @throws ParseException If the HTTP request couldn't be parsed to an 
1777         *                        OpenID Connect authentication request.
1778         */
1779        public static AuthenticationRequest parse(final HTTPRequest httpRequest)
1780                throws ParseException {
1781                
1782                String query = httpRequest.getQuery();
1783                
1784                if (query == null)
1785                        throw new ParseException("Missing URI query string");
1786
1787                URI endpointURI;
1788
1789                try {
1790                        endpointURI = httpRequest.getURL().toURI();
1791
1792                } catch (URISyntaxException e) {
1793
1794                        throw new ParseException(e.getMessage(), e);
1795                }
1796                
1797                return parse(endpointURI, query);
1798        }
1799}