Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Danaq's avatar
Level 1

Passport - Password Grant Token using custom request

According to my other question which could probably help others to configure their passport, I could isolate the problem.

https://laracasts.com/discuss/channels/laravel/passport-in-laravel-55-grant-type-not-supported

    public function login(ApiRequest\AppLoginRequest $request) {
        return $this->issueToken($request, 'password');
    }

As you can see here I'm using a custom request in the login-method called by the first request on the api.

This method calls another method included via a trait into the loginController

public function issueToken(Request $request, $grant_type, $scope = '*') {
        $params = [
            'grant_type' => $grant_type,
            'client_id' => $this->client->id,
            'client_secret' => $this->client->secret,
            'username' => $request->username ?: $request->email,
            'password' => $request->password,
            'scope' => $scope,
        ];
        
        $proxy = $request::create('oauth/token', 'POST');
        $proxy->request->add($params);
// dd($proxy);
        return Route::dispatch($proxy);
    }

First, I'm creating a new request. This one has no values in the request-attribute. But it' created from the "old" request-object inside $request which is a custom request.

After that the $params-array is added to the "new" request. When I DumpAndDie the $proxy variable, this is shown:

AppLoginRequest {#244
  #container: null
  #redirector: null
  #redirect: null
  #redirectRoute: null
  #redirectAction: null
  #errorBag: "default"
  #json: null
  #convertedFiles: null
  #userResolver: null
  #routeResolver: null
  +attributes: ParameterBag {#269
    #parameters: []
  }
  +request: ParameterBag {#247
    #parameters: array:6 [
      "grant_type" => "password"
      "client_id" => 1
      "client_secret" => "5SMtQmPo2wKyt6OxWZvRQIhG2lw4n2zxMRxW1nMo"
      "username" => "[email protected]"
      "password" => "myPassword"
      "scope" => "*"
    ]
  }
  +query: ParameterBag {#248
    #parameters: []
  }
  +server: ServerBag {#271
    #parameters: array:17 [
      "SERVER_NAME" => "localhost"
      "SERVER_PORT" => 80
      "HTTP_HOST" => "localhost"
      "HTTP_USER_AGENT" => "Symfony/3.X"
      "HTTP_ACCEPT" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
      "HTTP_ACCEPT_LANGUAGE" => "en-us,en;q=0.5"
      "HTTP_ACCEPT_CHARSET" => "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
      "REMOTE_ADDR" => "127.0.0.1"
      "SCRIPT_NAME" => ""
      "SCRIPT_FILENAME" => ""
      "SERVER_PROTOCOL" => "HTTP/1.1"
      "REQUEST_TIME" => 1520355343
      "PATH_INFO" => ""
      "REQUEST_METHOD" => "POST"
      "CONTENT_TYPE" => "application/x-www-form-urlencoded"
      "REQUEST_URI" => "oauth/token"
      "QUERY_STRING" => ""
    ]
  }
  +files: FileBag {#272
    #parameters: []
  }
  +cookies: ParameterBag {#270
    #parameters: []
  }
  +headers: HeaderBag {#273
    #headers: array:6 [
      "host" => array:1 [
        0 => "localhost"
      ]
      "user-agent" => array:1 [
        0 => "Symfony/3.X"
      ]
      "accept" => array:1 [
        0 => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
      ]
      "accept-language" => array:1 [
        0 => "en-us,en;q=0.5"
      ]
      "accept-charset" => array:1 [
        0 => "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
      ]
      "content-type" => array:1 [
        0 => "application/x-www-form-urlencoded"
      ]
    ]
    #cacheControl: []
  }
  #content: null
  #languages: null
  #charsets: null
  #encodings: null
  #acceptableContentTypes: null
  #pathInfo: null
  #requestUri: null
  #baseUrl: null
  #basePath: null
  #method: null
  #format: null
  #session: null
  #locale: null
  #defaultLocale: "en"
  -isHostValid: true
  -isForwardedValid: true
  pathInfo: "/oauth/token"
  requestUri: "oauth/token"
  baseUrl: ""
  basePath: ""
  method: "POST"
  format: "html"
}

So everything needed is there. The next target is also present.

The oauth/token route points to AccessTokenController@issueToken

This method looks like this:

    public function issueToken(ServerRequestInterface $request)
    {
        dd($request);
        return $this->withErrorHandling(function () use ($request) {
            return $this->convertResponse(
                $this->server->respondToAccessTokenRequest($request, new Psr7Response)
            );
        });
    }

While this is a package-file, I changed nothing here except the dd(). So, they're using another kind of request here. When I dump it

ServerRequest {#327
  -attributes: array:3 [
    "hasAppAccount" => true
    "isSubscriber" => false
    "subDevice" => false
  ]
  -cookieParams: array:1 [
    "apidev_session" => "eyJpdiI6IkFxYXJIUmM0U2g3OGtoOXlzRmZpMkE9PSIsInZhbHVlIjoiS0tRK2h1OHRORWFNcGE2SmJweFJnTG9CV3UzdDB0bjlWTjN2YzRPc0hzZkxIcHRGeVZueU01cDQxeWJcL0hSS3RJOEVcL2FPNXFYV3ZNdit1MXVYTENmUT09IiwibWFjIjoiMjQ4YWRiZjc4ZDhhYzQ1NDQwNGYyMjExM2YyOGY0YzRjNzdjYzYzYjI5NDhhNDk4MDI5MWE5NmMyNDNhZTcxNiJ9"
  ]
  -parsedBody: array:6 [
    "email" => "[email protected]"
    "password" => "myPassword"
    "device_id" => "1"
    "uuid" => "1"
    "built_version" => "1"
    "newsletter" => "1"
  ]
  -queryParams: []
  -serverParams: array:30 [
    "DOCUMENT_ROOT" => "/var/www/html/ideegoapi/public"
    "REMOTE_ADDR" => "192.168.56.1"
    "REMOTE_PORT" => "56362"
    "SERVER_SOFTWARE" => "PHP 7.0.22-0ubuntu0.16.04.1 Development Server"
    "SERVER_PROTOCOL" => "HTTP/1.1"
    "SERVER_NAME" => "192.168.56.101"
    "SERVER_PORT" => "8080"
    "REQUEST_URI" => "/api/1/login"
    "REQUEST_METHOD" => "POST"
    "SCRIPT_NAME" => "/index.php"
    "SCRIPT_FILENAME" => "/var/www/html/project/public/index.php"
    "PATH_INFO" => "/api/1/login"
    "PHP_SELF" => "/index.php/api/1/login"
    "HTTP_HOST" => "192.168.56.101:8080"
    "HTTP_CONNECTION" => "keep-alive"
    "CONTENT_LENGTH" => "644"
    "HTTP_CONTENT_LENGTH" => "644"
    "HTTP_ACCEPT" => "application/json"
    "HTTP_CACHE_CONTROL" => "no-cache"
    "HTTP_ORIGIN" => "chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop"
    "HTTP_USER_AGENT" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"
    "HTTP_POSTMAN_TOKEN" => "ab863501-5eab-5c05-7a96-a35ce8fea658"
    "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryxtIQOdKajUI8BYQ2"
    "HTTP_CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryxtIQOdKajUI8BYQ2"
    "HTTP_DNT" => "1"
    "HTTP_ACCEPT_ENCODING" => "gzip, deflate"
    "HTTP_ACCEPT_LANGUAGE" => "en,de-DE;q=0.9,de;q=0.8,en-US;q=0.7"
    "HTTP_COOKIE" => "apidev_session=eyJpdiI6IkFxYXJIUmM0U2g3OGtoOXlzRmZpMkE9PSIsInZhbHVlIjoiS0tRK2h1OHRORWFNcGE2SmJweFJnTG9CV3UzdDB0bjlWTjN2YzRPc0hzZkxIcHRGeVZueU01cDQxeWJcL0hSS3RJOEVcL2FPNXFYV3ZNdit1MXVYTENmUT09IiwibWFjIjoiMjQ4YWRiZjc4ZDhhYzQ1NDQwNGYyMjExM2YyOGY0YzRjNzdjYzYzYjI5NDhhNDk4MDI5MWE5NmMyNDNhZTcxNiJ9"
    "REQUEST_TIME_FLOAT" => 1520355546.0966
    "REQUEST_TIME" => 1520355546
  ]
  -uploadedFiles: []
  -method: "POST"
  -requestTarget: "/api/1/login"
  -uri: Uri {#328
    #allowedSchemes: array:2 [
      "http" => 80
      "https" => 443
    ]
    -scheme: "http"
    -userInfo: ""
    -host: "192.168.56.101"
    -port: 8080
    -path: "/api/1/login"
    -query: ""
    -fragment: ""
    -uriString: null
  }
  #headers: array:13 [
    "host" => array:1 [
      0 => "192.168.56.101:8080"
    ]
    "connection" => array:1 [
      0 => "keep-alive"
    ]
    "content-length" => array:1 [
      0 => "644"
    ]
    "accept" => array:1 [
      0 => "application/json"
    ]
    "cache-control" => array:1 [
      0 => "no-cache"
    ]
    "origin" => array:1 [
      0 => "chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop"
    ]
    "user-agent" => array:1 [
      0 => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"
    ]
    "postman-token" => array:1 [
      0 => "ab863501-5eab-5c05-7a96-a35ce8fea658"
    ]
    "content-type" => array:1 [
      0 => "multipart/form-data; boundary=----WebKitFormBoundaryxtIQOdKajUI8BYQ2"
    ]
    "dnt" => array:1 [
      0 => "1"
    ]
    "accept-encoding" => array:1 [
      0 => "gzip, deflate"
    ]
    "accept-language" => array:1 [
      0 => "en,de-DE;q=0.9,de;q=0.8,en-US;q=0.7"
    ]
    "cookie" => array:1 [
      0 => "apidev_session=eyJpdiI6IkFxYXJIUmM0U2g3OGtoOXlzRmZpMkE9PSIsInZhbHVlIjoiS0tRK2h1OHRORWFNcGE2SmJweFJnTG9CV3UzdDB0bjlWTjN2YzRPc0hzZkxIcHRGeVZueU01cDQxeWJcL0hSS3RJOEVcL2FPNXFYV3ZNdit1MXVYTENmUT09IiwibWFjIjoiMjQ4YWRiZjc4ZDhhYzQ1NDQwNGYyMjExM2YyOGY0YzRjNzdjYzYzYjI5NDhhNDk4MDI5MWE5NmMyNDNhZTcxNiJ9"
    ]
  ]
  #headerNames: array:13 [
    "host" => "host"
    "connection" => "connection"
    "content-length" => "content-length"
    "accept" => "accept"
    "cache-control" => "cache-control"
    "origin" => "origin"
    "user-agent" => "user-agent"
    "postman-token" => "postman-token"
    "content-type" => "content-type"
    "dnt" => "dnt"
    "accept-encoding" => "accept-encoding"
    "accept-language" => "accept-language"
    "cookie" => "cookie"
  ]
  -protocol: "1.1"
  -stream: Stream {#319
    #resource: stream resource @22
      wrapper_type: "PHP"
      stream_type: "TEMP"
      mode: "w+b"
      unread_bytes: 0
      seekable: true
      uri: "php://temp"
      options: []
    }
    #stream: null
  }
}

So my forwarded proxy-request from above changed to this one where no request-data is present.

I still get the error

{
    "error": "unsupported_grant_type",
    "message": "The authorization grant type is not supported by the authorization server.",
    "hint": "Check the `grant_type` parameter"
}

when calling the login-route.

The thing is, if I using the default laravel request in the loginController like that:

public function login(Request $request) {
        return $this->issueToken($request, 'password');
    }

Everything works perfectly. But I want to use custom requests to encapsulate them and split them from the logic of the controller. My AppLoginRequest succeeds from the FormRequest (like laravel is creating a custom request class) which, on its part, succeeds from the "normal" request class of laravel again.

So, when I got the thing right with suceeding in PHP, my AppLoginRequest-class is also a Request-class.

Why this is not working? Is there anyone with an idea?

0 likes
5 replies
tylik's avatar

I have the same issue. But when i call "oauth/token" directly from Postman, there is no such error and token is received as expected.

Have you solved it? Also i can see that many people used proxy and didn't experience this issue, which is very strange.

Danaq's avatar
Level 1

No I didn't find a solution. The direct call works also for me. But I don't want to store the client_secret on the mobile-app.

I tried many different ways

public function issueToken(Request $request, $grant_type, $scope = '*') {
 $params = [
            'grant_type' => $grant_type,
            'client_id' => $this->client->id,
            'client_secret' => $this->client->secret,
            'username' => $request->username ?: $request->email,
            'password' => $request->password,
            'scope' => $scope,
        ];
        
        $request->request->add($params);        
        $proxy = Request::create('api/oauth/token', 'POST');

        return Route::dispatch($proxy);

This is my current token-method called from the LoginController. The route the request is forwarded to is now directly written to the api-routes-file. I know in case of an update I have to adapt this route but I only use this only route and I wanted to add more middlewares to it because I need imformation from those even in the forwarded request. The login-mehtod there looks like this

public function login(Request $request) {
        $request->validate([
            'email' => 'required|string|email|max:255|',
            'password' => [
                'required',
                'string',
                'min:8',
                Rule::notIn([$request->email])
            ],
            'device_id' => 'required|alpha_num|max:255',
            'uuid' => 'required|alpha_num|max:50',
            'built_version' => 'required|alpha_num|max:50'
        ]);
        
        $accountSet = new Controllers\AccountController();
        $accountSet->registerUserForApp(Auth::id(), $request);
        
        return $this->issueToken($request, 'password');
    }

As you can see I'm using the default Request object now.

Here the variations I tried ($params is always set):

    $proxy = CustomRequest::create('api/oauth/token', 'POST');
    $proxy->request->add($params);

        return Route::dispatch($proxy);
//issueToken receives an instance of CustomRequest as $request
    
    $proxy = $request::create('api/oauth/token', 'POST');
    $proxy->request->add($params);

        return Route::dispatch($proxy);

I also tried to change the request-url in the given CustomRequest-object and pass this one to the dispatcher... nothing.

Everytime I use another instance of Request then the default one, the issueToken(ServerRequestInterface $request)-method of the oauth/token route receives a request-object without the data I added in the loginController.

Anywhere inside this magic laravel-thing this data is stored for the next request, I believe.

michaelnguyen547's avatar

Same problem and it is driving me crazy

// instead of Route::dispatch($proxy)

// use this, it will return a Response object which you can use getContent() to retrieve whatever and build up your own response from it
app()->handle($proxy);

// ideally, i want it just return id, expired_in, access_token and refresh_token as I would call /oauth/token directly
Abdelhafz's avatar

it seem to be late but you can use that it achieve both custom request and issue the token


public function login(Request $request, LoginRequest $req ) {
// if you want to get validated data
	$data = $req->validated();
//if you don't you have not to type $req->validated() since it will validate custom request automatically

	return $this->issueToken($request, 'password');

}

same in register


 public function register(Request $request, RegisterRequest $req){
	$req->validated();

	$user = User::create([
            'email'             =>      $request->email,
            'username'          =>      $request->username,
            'password'          =>      bcrypt($request->password),
        ]);
	return response()->success(__('messages.SUCCESSFULY_REGISTERED'), [
            'token' => $this->getAccessToken($this->issueAccessToken($request)),
            'user' => $user->fresh(),
        ]);

}

Please or to participate in this conversation.