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