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