001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2023, 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.oauth2.sdk;
019
020
021import com.nimbusds.common.contenttype.ContentType;
022import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
023import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
024import com.nimbusds.oauth2.sdk.http.HTTPRequest;
025import com.nimbusds.oauth2.sdk.id.ClientID;
026import com.nimbusds.oauth2.sdk.rar.AuthorizationDetail;
027import com.nimbusds.oauth2.sdk.token.RefreshToken;
028import com.nimbusds.oauth2.sdk.util.*;
029import net.jcip.annotations.Immutable;
030
031import java.net.URI;
032import java.net.URISyntaxException;
033import java.util.*;
034
035
036/**
037 * Token request. Used to obtain an
038 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an
039 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token}
040 * at the Token endpoint of the authorisation server. Supports custom request
041 * parameters.
042 *
043 * <p>Example token request with an authorisation code grant:
044 *
045 * <pre>
046 * POST /token HTTP/1.1
047 * Host: server.example.com
048 * Content-Type: application/x-www-form-urlencoded
049 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
050 *
051 * grant_type=authorization_code
052 * &amp;code=SplxlOBeZQQYbYS6WxSbIA
053 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
054 * </pre>
055 *
056 * <p>Related specifications:
057 *
058 * <ul>
059 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2, 4.4.2 and 6.
060 *     <li>OAuth 2.0 Rich Authorization Requests (RFC 9396), section 6.
061 *     <li>Resource Indicators for OAuth 2.0 (RFC 8707)
062 *     <li>OAuth 2.0 Incremental Authorization
063 *         (draft-ietf-oauth-incremental-authz-04)
064 * </ul>
065 */
066@Immutable
067public class TokenRequest extends AbstractOptionallyIdentifiedRequest {
068
069
070        /**
071         * The authorisation grant.
072         */
073        private final AuthorizationGrant authzGrant;
074
075
076        /**
077         * The requested scope, {@code null} if not specified.
078         */
079        private final Scope scope;
080
081
082        /**
083         * The RAR details (optional).
084         */
085        private final List<AuthorizationDetail> authorizationDetails;
086        
087        
088        /**
089         * The resource URI(s), {@code null} if not specified.
090         */
091        private final List<URI> resources;
092        
093        
094        /**
095         * Existing refresh token for incremental authorisation of a public
096         * client, {@code null} if not specified.
097         */
098        private final RefreshToken existingGrant;
099
100
101        /**
102         * Custom request parameters.
103         */
104        private final Map<String,List<String>> customParams;
105
106
107        private static final Set<String> ALLOWED_REPEATED_PARAMS = new HashSet<>(Arrays.asList("resource", "audience"));
108
109
110        /**
111         * Creates a new token request with the specified client
112         * authentication.
113         *
114         * @param uri        The URI of the token endpoint. May be
115         *                   {@code null} if the {@link #toHTTPRequest} method
116         *                   will not be used.
117         * @param clientAuth The client authentication. Must not be
118         *                   {@code null}.
119         * @param authzGrant The authorisation grant. Must not be {@code null}.
120         * @param scope      The requested scope, {@code null} if not
121         *                   specified.
122         */
123        public TokenRequest(final URI uri,
124                            final ClientAuthentication clientAuth,
125                            final AuthorizationGrant authzGrant,
126                            final Scope scope) {
127
128                this(uri, clientAuth, authzGrant, scope, null, null);
129        }
130
131
132        /**
133         * Creates a new token request with the specified client
134         * authentication and extension and custom parameters.
135         *
136         * @param uri          The URI of the token endpoint. May be
137         *                     {@code null} if the {@link #toHTTPRequest}
138         *                     method will not be used.
139         * @param clientAuth   The client authentication. Must not be
140         *                     {@code null}.
141         * @param authzGrant   The authorisation grant. Must not be
142         *                     {@code null}.
143         * @param scope        The requested scope, {@code null} if not
144         *                     specified.
145         * @param resources    The resource URI(s), {@code null} if not
146         *                     specified.
147         * @param customParams Custom parameters to be included in the request
148         *                     body, empty map or {@code null} if none.
149         */
150        public TokenRequest(final URI uri,
151                            final ClientAuthentication clientAuth,
152                            final AuthorizationGrant authzGrant,
153                            final Scope scope,
154                            final List<URI> resources,
155                            final Map<String,List<String>> customParams) {
156
157                this(uri, clientAuth, authzGrant, scope, null, resources, customParams);
158        }
159
160
161        /**
162         * Creates a new token request with the specified client
163         * authentication and extension and custom parameters.
164         *
165         * @param uri                  The URI of the token endpoint. May be
166         *                             {@code null} if the
167         *                             {@link #toHTTPRequest} method will not
168         *                             be used.
169         * @param clientAuth           The client authentication. Must not be
170         *                             {@code null}.
171         * @param authzGrant           The authorisation grant. Must not be
172         *                             {@code null}.
173         * @param scope                The requested scope, {@code null} if not
174         *                             specified.
175         * @param authorizationDetails The Rich Authorisation Request (RAR)
176         *                             details, {@code null} if not specified.
177         * @param resources            The resource URI(s), {@code null} if not
178         *                             specified.
179         * @param customParams         Custom parameters to be included in the
180         *                             request body, empty map or {@code null}
181         *                             if none.
182         */
183        public TokenRequest(final URI uri,
184                            final ClientAuthentication clientAuth,
185                            final AuthorizationGrant authzGrant,
186                            final Scope scope,
187                            final List<AuthorizationDetail> authorizationDetails,
188                            final List<URI> resources,
189                            final Map<String,List<String>> customParams) {
190
191                super(uri, clientAuth);
192
193                if (clientAuth == null)
194                        throw new IllegalArgumentException("The client authentication must not be null");
195
196                this.authzGrant = authzGrant;
197
198                this.scope = scope;
199
200                if (resources != null) {
201                        for (URI resourceURI: resources) {
202                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
203                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
204                        }
205                }
206
207                this.authorizationDetails = authorizationDetails;
208
209                this.resources = resources;
210
211                this.existingGrant = null; // only for confidential client
212
213                if (MapUtils.isNotEmpty(customParams)) {
214                        this.customParams = customParams;
215                } else {
216                        this.customParams = Collections.emptyMap();
217                }
218        }
219
220
221        /**
222         * Creates a new token request with the specified client
223         * authentication.
224         *
225         * @param uri        The URI of the token endpoint. May be
226         *                   {@code null} if the {@link #toHTTPRequest} method
227         *                   will not be used.
228         * @param clientAuth The client authentication. Must not be
229         *                   {@code null}.
230         * @param authzGrant The authorisation grant. Must not be {@code null}.
231         */
232        public TokenRequest(final URI uri,
233                            final ClientAuthentication clientAuth,
234                            final AuthorizationGrant authzGrant) {
235
236                this(uri, clientAuth, authzGrant, null);
237        }
238
239
240        /**
241         * Creates a new token request, with no explicit client authentication
242         * (maybe present in the grant depending on its type).
243         *
244         * @param uri        The URI of the token endpoint. May be
245         *                   {@code null} if the {@link #toHTTPRequest} method
246         *                   will not be used.
247         * @param clientID   The client identifier, {@code null} if not
248         *                   specified.
249         * @param authzGrant The authorisation grant. Must not be {@code null}.
250         * @param scope      The requested scope, {@code null} if not
251         *                   specified.
252         */
253        public TokenRequest(final URI uri,
254                            final ClientID clientID,
255                            final AuthorizationGrant authzGrant,
256                            final Scope scope) {
257
258                this(uri, clientID, authzGrant, scope, null, null,null);
259        }
260
261
262        /**
263         * Creates a new token request, with no explicit client authentication
264         * (maybe present in the grant depending on its type) and extension
265         * and custom parameters.
266         *
267         * @param uri           The URI of the token endpoint. May be
268         *                      {@code null} if the {@link #toHTTPRequest}
269         *                      method will not be used.
270         * @param clientID      The client identifier, {@code null} if not
271         *                      specified.
272         * @param authzGrant    The authorisation grant. Must not be
273         *                      {@code null}.
274         * @param scope         The requested scope, {@code null} if not
275         *                      specified.
276         * @param resources     The resource URI(s), {@code null} if not
277         *                      specified.
278         * @param existingGrant Existing refresh token for incremental
279         *                      authorisation of a public client, {@code null}
280         *                      if not specified.
281         * @param customParams  Custom parameters to be included in the request
282         *                      body, empty map or {@code null} if none.
283         */
284        public TokenRequest(final URI uri,
285                            final ClientID clientID,
286                            final AuthorizationGrant authzGrant,
287                            final Scope scope,
288                            final List<URI> resources,
289                            final RefreshToken existingGrant,
290                            final Map<String,List<String>> customParams) {
291
292                this(uri, clientID, authzGrant, scope, null, resources, existingGrant, customParams);
293        }
294
295
296        /**
297         * Creates a new token request, with no explicit client authentication
298         * (maybe present in the grant depending on its type) and extension
299         * and custom parameters.
300         *
301         * @param uri                  The URI of the token endpoint. May be
302         *                             {@code null} if the
303         *                             {@link #toHTTPRequest}
304         *                             method will not be used.
305         * @param clientID             The client identifier, {@code null} if
306         *                             not specified.
307         * @param authzGrant           The authorisation grant. Must not be
308         *                             {@code null}.
309         * @param scope                The requested scope, {@code null} if not
310         *                             specified.
311         * @param authorizationDetails The Rich Authorisation Request (RAR)
312         *                             details, {@code null} if not specified.
313         * @param resources            The resource URI(s), {@code null} if not
314         *                             specified.
315         * @param existingGrant        Existing refresh token for incremental
316         *                             authorisation of a public client,
317         *                             {@code null} if not specified.
318         * @param customParams         Custom parameters to be included in the
319         *                             request body, empty map or {@code null}
320         *                             if none.
321         */
322        public TokenRequest(final URI uri,
323                            final ClientID clientID,
324                            final AuthorizationGrant authzGrant,
325                            final Scope scope,
326                            final List<AuthorizationDetail> authorizationDetails,
327                            final List<URI> resources,
328                            final RefreshToken existingGrant,
329                            final Map<String,List<String>> customParams) {
330
331                super(uri, clientID);
332
333                if (authzGrant.getType().requiresClientAuthentication()) {
334                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
335                }
336
337                if (authzGrant.getType().requiresClientID() && clientID == null) {
338                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
339                }
340
341                this.authzGrant = authzGrant;
342
343                this.scope = scope;
344
345                this.authorizationDetails = authorizationDetails;
346
347                if (resources != null) {
348                        for (URI resourceURI: resources) {
349                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
350                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
351                        }
352                }
353
354                this.resources = resources;
355
356                this.existingGrant = existingGrant;
357
358                if (MapUtils.isNotEmpty(customParams)) {
359                        this.customParams = customParams;
360                } else {
361                        this.customParams = Collections.emptyMap();
362                }
363        }
364
365
366        /**
367         * Creates a new token request, with no explicit client authentication
368         * (maybe present in the grant depending on its type).
369         *
370         * @param uri        The URI of the token endpoint. May be
371         *                   {@code null} if the {@link #toHTTPRequest} method
372         *                   will not be used.
373         * @param clientID   The client identifier, {@code null} if not
374         *                   specified.
375         * @param authzGrant The authorisation grant. Must not be {@code null}.
376         */
377        public TokenRequest(final URI uri,
378                            final ClientID clientID,
379                            final AuthorizationGrant authzGrant) {
380
381                this(uri, clientID, authzGrant, null);
382        }
383
384
385        /**
386         * Creates a new token request, without client authentication and a
387         * specified client identifier.
388         *
389         * @param uri        The URI of the token endpoint. May be
390         *                   {@code null} if the {@link #toHTTPRequest} method
391         *                   will not be used.
392         * @param authzGrant The authorisation grant. Must not be {@code null}.
393         * @param scope      The requested scope, {@code null} if not
394         *                   specified.
395         */
396        public TokenRequest(final URI uri,
397                            final AuthorizationGrant authzGrant,
398                            final Scope scope) {
399
400                this(uri, (ClientID)null, authzGrant, scope);
401        }
402
403
404        /**
405         * Creates a new token request, without client authentication and a
406         * specified client identifier.
407         *
408         * @param uri        The URI of the token endpoint. May be
409         *                   {@code null} if the {@link #toHTTPRequest} method
410         *                   will not be used.
411         * @param authzGrant The authorisation grant. Must not be {@code null}.
412         */
413        public TokenRequest(final URI uri,
414                            final AuthorizationGrant authzGrant) {
415
416                this(uri, (ClientID)null, authzGrant, null);
417        }
418
419
420        /**
421         * Returns the authorisation grant.
422         *
423         * @return The authorisation grant.
424         */
425        public AuthorizationGrant getAuthorizationGrant() {
426
427                return authzGrant;
428        }
429
430
431        /**
432         * Returns the requested scope.
433         *
434         * @return The requested scope, {@code null} if not specified.
435         */
436        public Scope getScope() {
437
438                return scope;
439        }
440
441
442        /**
443         * Returns the Rich Authorisation Request (RAR) details.
444         *
445         * @return The authorisation details, {@code null} if not specified.
446         */
447        public List<AuthorizationDetail> getAuthorizationDetails() {
448
449                return authorizationDetails;
450        }
451        
452        
453        /**
454         * Returns the resource server URI.
455         *
456         * @return The resource URI(s), {@code null} if not specified.
457         */
458        public List<URI> getResources() {
459                
460                return resources;
461        }
462        
463        
464        /**
465         * Returns the existing refresh token for incremental authorisation of
466         * a public client, {@code null} if not specified.
467         *
468         * @return The existing grant, {@code null} if not specified.
469         */
470        public RefreshToken getExistingGrant() {
471                
472                return existingGrant;
473        }
474
475
476        /**
477         * Returns the additional custom parameters included in the request
478         * body.
479         *
480         * <p>Example:
481         *
482         * <pre>
483         * resource=http://xxxxxx/PartyOData
484         * </pre>
485         *
486         * @return The additional custom parameters as an unmodifiable map,
487         *         empty map if none.
488         */
489        public Map<String,List<String>> getCustomParameters () {
490
491                return Collections.unmodifiableMap(customParams);
492        }
493
494
495        /**
496         * Returns the specified custom parameter included in the request body.
497         *
498         * @param name The parameter name. Must not be {@code null}.
499         *
500         * @return The parameter value(s), {@code null} if not specified.
501         */
502        public List<String> getCustomParameter(final String name) {
503
504                return customParams.get(name);
505        }
506
507
508        @Override
509        public HTTPRequest toHTTPRequest() {
510
511                if (getEndpointURI() == null)
512                        throw new SerializeException("The endpoint URI is not specified");
513
514                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
515                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
516
517                if (getClientAuthentication() != null) {
518                        getClientAuthentication().applyTo(httpRequest);
519                }
520
521                Map<String, List<String>> params;
522                try {
523                        params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters());
524                } catch (ParseException e) {
525                        throw new SerializeException(e.getMessage(), e);
526                }
527                params.putAll(authzGrant.toParameters());
528
529                switch (authzGrant.getType().getScopeRequirementInTokenRequest()) {
530                        case REQUIRED:
531                                if (CollectionUtils.isEmpty(scope)) {
532                                        throw new SerializeException("Scope is required for the " + authzGrant.getType() + " grant");
533                                }
534                                params.put("scope", Collections.singletonList(scope.toString()));
535                                break;
536                        case OPTIONAL:
537                                if (CollectionUtils.isNotEmpty(scope)) {
538                                        params.put("scope", Collections.singletonList(scope.toString()));
539                                }
540                                break;
541                        case NOT_ALLOWED:
542                        default:
543                                break;
544                }
545
546                if (getClientID() != null) {
547                        params.put("client_id", Collections.singletonList(getClientID().getValue()));
548                }
549
550                if (getAuthorizationDetails() != null) {
551                        params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails())));
552                }
553                
554                if (getResources() != null) {
555                        List<String> values = new LinkedList<>();
556                        for (URI uri: resources) {
557                                if (uri == null)
558                                        continue;
559                                values.add(uri.toString());
560                        }
561                        params.put("resource", values);
562                }
563                
564                if (getExistingGrant() != null) {
565                        params.put("existing_grant", Collections.singletonList(existingGrant.getValue()));
566                }
567
568                if (! getCustomParameters().isEmpty()) {
569                        params.putAll(getCustomParameters());
570                }
571
572                httpRequest.setBody(URLUtils.serializeParameters(params));
573
574                return httpRequest;
575        }
576
577
578        /**
579         * Parses a token request from the specified HTTP request.
580         *
581         * @param httpRequest The HTTP request. Must not be {@code null}.
582         *
583         * @return The token request.
584         *
585         * @throws ParseException If the HTTP request couldn't be parsed to a
586         *                        token request.
587         */
588        public static TokenRequest parse(final HTTPRequest httpRequest)
589                throws ParseException {
590
591                // Only HTTP POST accepted
592                URI uri = httpRequest.getURI();
593                httpRequest.ensureMethod(HTTPRequest.Method.POST);
594                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
595
596                // Parse client authentication, if any
597                ClientAuthentication clientAuth;
598                
599                try {
600                        clientAuth = ClientAuthentication.parse(httpRequest);
601                } catch (ParseException e) {
602                        throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
603                }
604
605                // No fragment! May use query component!
606                Map<String,List<String>> params = httpRequest.getBodyAsFormParameters();
607                
608                Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS);
609                if (! repeatParams.isEmpty()) {
610                        String msg = "Parameter(s) present more than once: " + repeatParams;
611                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg));
612                }
613                
614                // Multiple conflicting client auth methods (issue #203)?
615                if (clientAuth instanceof ClientSecretBasic) {
616                        if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) {
617                                String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion";
618                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
619                        }
620                }
621
622                // Parse grant
623                AuthorizationGrant grant = AuthorizationGrant.parse(params);
624
625                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
626                        String msg = "Missing client authentication";
627                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
628                }
629
630                // Parse client id
631                ClientID clientID = null;
632
633                if (clientAuth == null) {
634
635                        // Parse optional client ID
636                        String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id");
637
638                        if (clientIDString != null && ! clientIDString.trim().isEmpty())
639                                clientID = new ClientID(clientIDString);
640
641                        if (clientID == null && grant.getType().requiresClientID()) {
642                                String msg = "Missing required client_id parameter";
643                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
644                        }
645                }
646
647                // Parse optional scope
648                String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope");
649
650                ParameterRequirement scopeRequirement = grant.getType().getScopeRequirementInTokenRequest();
651
652                Scope scope = null;
653
654                if (scopeValue != null && (ParameterRequirement.REQUIRED.equals(scopeRequirement) || ParameterRequirement.OPTIONAL.equals(scopeRequirement))) {
655                        scope = Scope.parse(scopeValue);
656                }
657
658                // Parse optional RAR
659                String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details");
660
661                List<AuthorizationDetail> authorizationDetails = null;
662
663                if (json != null) {
664                        authorizationDetails = AuthorizationDetail.parseList(json);
665                }
666                
667                // Parse optional resource URIs
668                List<URI> resources = null;
669                
670                List<String> vList = params.get("resource");
671                
672                if (vList != null) {
673                        
674                        resources = new LinkedList<>();
675                        
676                        for (String uriValue: vList) {
677                                
678                                if (uriValue == null)
679                                        continue;
680                                
681                                String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue;
682                                
683                                URI resourceURI;
684                                try {
685                                        resourceURI = new URI(uriValue);
686                                } catch (URISyntaxException e) {
687                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
688                                }
689                                
690                                if (! ResourceUtils.isLegalResourceURI(resourceURI)) {
691                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
692                                }
693                                
694                                resources.add(resourceURI);
695                        }
696                }
697                
698                String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant");
699                RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null;
700
701                // Parse custom parameters
702                Map<String,List<String>> customParams = new HashMap<>();
703
704                for (Map.Entry<String,List<String>> p: params.entrySet()) {
705
706                        if (p.getKey().equalsIgnoreCase("grant_type")) {
707                                continue; // skip
708                        }
709
710                        if (p.getKey().equalsIgnoreCase("client_id")) {
711                                continue; // skip
712                        }
713
714                        if (p.getKey().equalsIgnoreCase("client_secret")) {
715                                continue; // skip
716                        }
717
718                        if (p.getKey().equalsIgnoreCase("client_assertion_type")) {
719                                continue; // skip
720                        }
721
722                        if (p.getKey().equalsIgnoreCase("client_assertion")) {
723                                continue; // skip
724                        }
725
726                        if (p.getKey().equalsIgnoreCase("scope")) {
727                                continue; // skip
728                        }
729
730                        if (p.getKey().equalsIgnoreCase("authorization_details")) {
731                                continue; // skip
732                        }
733                        
734                        if (p.getKey().equalsIgnoreCase("resource")) {
735                                continue; // skip
736                        }
737                        
738                        if (p.getKey().equalsIgnoreCase("existing_grant"))
739                                continue; // skip
740
741                        if (! grant.getType().getRequestParameterNames().contains(p.getKey())) {
742                                // We have a custom (non-registered) parameter
743                                customParams.put(p.getKey(), p.getValue());
744                        }
745                }
746
747                if (clientAuth != null) {
748                        return new TokenRequest(uri, clientAuth, grant, scope, authorizationDetails, resources, customParams);
749                } else {
750                        // public client
751                        return new TokenRequest(uri, clientID, grant, scope, authorizationDetails, resources, existingGrant, customParams);
752                }
753        }
754}