Quantcast
Channel: Active questions tagged rest - Stack Overflow
Viewing all articles
Browse latest Browse all 3641

"Invalid state parameter passed in callback URL" error implementing Google OAuth2 in an API REST (PHP & Symfony)

$
0
0

I am developing a REST API with PHP 8.2, Symfony 6.4 and API Platform. I am finishing implementing OAuth with Google using the bundle KnpUOAuth2ClientBundle but I am getting the error that the title says. I have seen this problem in other threads but in my case and since it is an API, I do not have a front-end to redirect yet, but I don't know if that is really the problem.

I have divided my implementation into 3 classes: Client > Service > Controller. Below I leave the code and my configuration. (I will skip the class imports to not make it so long).

CLIENT

<?phpnamespace App\Http\Google;use League\OAuth2\Client\Provider\Exception\IdentityProviderException;use League\OAuth2\Client\Provider\Google;use League\OAuth2\Client\Token\AccessToken;class GoogleClient{    private Google $client;    public function __construct(        string $clientId,        string $clientSecret)    {        $this->client = new Google(['clientId' => $clientId,'clientSecret' => $clientSecret,'redirectUri' => 'http://localhost:250/api/v1/users/google/oauth',        ]);    }    /**     * @throws IdentityProviderException     */    public function getAccessToken(string $grant = 'authorization_code', array $options = []): AccessToken    {        return $this->client->getAccessToken($grant, $options);    }    public function getUserInfo(AccessToken $accessToken): array    {        $resourceOwner = $this->client->getResourceOwner($accessToken);        return $resourceOwner->toArray();    }}

SERVICE

The "authorize" function is the function that is responsible for checking if a user exists in the database. If it does not exist, call to the function "createUser" and creates it, returns a JSON token with which to authenticate created with Lexik JWTAuthenticationBundle bundle. As I said before, I still no have a front-end to test the routes, so I am testing it using the URL provided by Google:

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=(MY ID).apps.googleusercontent.com&redirect_uri=http://localhost:250/api/v1/users/google/oauth&scope=email%20profile&access_type=offline&prompt=consent

<?phpnamespace App\Service\Google;use App\Entity\User;use App\Exception\User\UserNotFoundException;use App\Http\Google\GoogleClient;use App\Repository\UserRepository;use App\Service\Password\EncoderService;use Doctrine\ORM\Exception\ORMException;use Doctrine\ORM\OptimisticLockException;use Exception;use KnpU\OAuth2ClientBundle\Client\ClientRegistry;use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;use Symfony\Component\Security\Core\Exception\AuthenticationException;use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;use Symfony\Component\Security\Http\Authenticator\Passport\Passport;use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;class GoogleService extends OAuth2Authenticator implements AuthenticationEntrypointInterface{    private ClientRegistry $clientRegistry;    private EncoderService $encoderService;    private GoogleClient $googleClient;    private JWTTokenManagerInterface $JWTTokenManager;    private UserRepository $userRepository;    public function __construct(        ClientRegistry $clientRegistry,        EncoderService $encoderService,        GoogleClient $googleClient,        JWTTokenManagerInterface $JWTTokenManager,        UserRepository $userRepository        )    {        $this->clientRegistry = $clientRegistry;        $this->encoderService = $encoderService;        $this->googleClient = $googleClient;        $this->JWTTokenManager = $JWTTokenManager;        $this->userRepository = $userRepository;    }    /**     * CUSTOM FUNCTIONS     */    /**     * @throws OptimisticLockException     * @throws ORMException     */    public function authorize(string $code): string    {        try {            $accessToken = $this->googleClient->getAccessToken('authorization_code', ['code' => $code]);            $userProfile = $this->googleClient->getUserInfo($accessToken);        } catch (Exception $exception) {            throw new BadRequestHttpException(sprintf('Google error. Message: %s', $exception->getMessage()));        }        $googleEmail = $userProfile['email'] ?? null;        $googleName = $userProfile['name'] ?? null;        if (null === $googleEmail) {            throw new BadRequestHttpException('Google account without E-Mail.');        }        try {            $user = $this->userRepository->findOneByEmailOrFail($googleEmail);        } catch (UserNotFoundException) {            $user = $this->createUser($googleName, $googleEmail);        }        return $this->JWTTokenManager->create($user);    }    /**     * @throws OptimisticLockException     * @throws ORMException     */    private function createUser(string $name, string $email): User    {        $user = new User($name, $email);        $user->setPassword($this->encoderService->generateEncodedPassword($user, \sha1(\uniqid())));        $user->setActive(true);        $user->setToken(null);        $this->userRepository->save($user);        return $user;    }    /**     * OAUTH2AUTHENTICATOR FUNCTIONS     */    public function supports(Request $request): ?bool    {        return $request->attributes->get('_route') === 'google_oauth';    }    public function authenticate(Request $request): Passport    {        $client = $this->clientRegistry->getClient('google');        $accessToken = $this->fetchAccessToken($client);        return new SelfValidatingPassport(            new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {                return $client->fetchUserFromToken($accessToken);            })        );    }    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response    {        return null;    }    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response    {        $message = strtr($exception->getMessageKey(), $exception->getMessageData());        return new Response(            $message, Response::HTTP_INTERNAL_SERVER_ERROR        );    }    public function start(Request $request, AuthenticationException $authException = null): Response    {        return new Response('JWT required', Response::HTTP_UNAUTHORIZED        );    }}

CONTROLLER

<?phpnamespace App\Controller\Action\Google;use App\Service\Google\GoogleService;use Doctrine\ORM\Exception\ORMException;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Attribute\AsController;#[AsController]class Authorization{    private GoogleService $googleService;    public function __construct(GoogleService $googleService)    {        $this->googleService = $googleService;    }    public function __invoke(Request $request): JsonResponse    {        $code = $request->query->get('code');        if (!$code) {            return new JsonResponse(                ['error' => 'Authorization code not provided.'],                Response::HTTP_FORBIDDEN            );        }        try {            $token = $this->googleService->authorize($code);            return new JsonResponse(                ['token' => $token]            );        } catch (ORMException $exception) {            return new JsonResponse(                ['error' => $exception->getMessage()],                Response::HTTP_INTERNAL_SERVER_ERROR            );        }    }}

API ENDPOINT

google_oauth:   class: ApiPlatform\Metadata\Get   controller: App\Controller\Action\Google\Authorization   deserialize: false   method: GET   uriTemplate: /users/google/oauth   openapi:      summary: OAuth with Google.      requestBody:         content:            application/json:               schema:                  type: object                  properties:                    code:                      type: string                      required: true         responses:            200:               description: OK               content:                  application/json:                     schema:                        type: object                        properties:                           token:                              type: string

SECURITY.YAML

As specified in documentation.

security:    enable_authenticator_manager: true    google_oauth:        pattern: ^/api/v1/users/google/oauth        methods: [GET]        custom_authenticators:            - App\Service\Google\GoogleService

KNPU_OAUTH2_CLIENT.YAML

knpu_oauth2_client:    clients:        google:            type: google            client_id: '%env(OAUTH_GOOGLE_CLIENT_ID)%'            client_secret: '%env(OAUTH_GOOGLE_CLIENT_SECRET)%'            redirect_route: google_oauth            redirect_params: {}

GOOGLE CLOUD CONSOLE ROUTES

The truth is that I don't know what I'm doing wrong and I don't know where to look anymore. This is the first time I have implemented a system like this and I don't see where the error could be.

Thanks in advance.

I have tried to check the functionality with the URL that Google provides for debugging: https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=(MY ID).apps.googleusercontent.com&redirect_uri=http://localhost:250/api/v1/users/google/oauth&scope=email%20profile&access_type=offline&prompt=consent

I have changed and checked the routes in the Google Cloud Console. I have also tried doing "var_dump" at different points in the code but it does not return anything.

I have also tried testing in different browsers (Google Chrome, Opera and Firefox), but I get the same result in all of them.


Viewing all articles
Browse latest Browse all 3641

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>