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