Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Invalid state parameter passed in callback URL" error in API REST context. #440

Open
GerRubio opened this issue Mar 7, 2024 · 1 comment

Comments

@GerRubio
Copy link

GerRubio commented Mar 7, 2024

Hello.

I am developing a REST API with API Platform and I am finishing implementing OAuth with Google 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

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, it creates it and in any case, returns a JSON token with which to authenticate.

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;
    }

    /**
     * @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;
    }

    public function supports(Request $request): ?bool
    {
        return $request->attributes->get('_route') === 'google_oauth';
    }

    public function authenticate(Request $request): SelfValidatingPassport
    {
        $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): ?JsonResponse
    {
        $user = $token->getUser();
        $userToken = $this->JWTTokenManager->create($user);

        return new JsonResponse(
            ['token' => $userToken]
        );
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?JsonResponse
    {
        $message = strtr($exception->getMessageKey(), $exception->getMessageData());

        return new JsonResponse(
            $message, Response::HTTP_INTERNAL_SERVER_ERROR
        );
    }

    public function start(Request $request, AuthenticationException $authException = null): ?JsonResponse
    {
        $message = strtr($authException->getMessageKey(), $authException->getMessageData());

        return new JsonResponse(
            $message, Response::HTTP_FORBIDDEN
        );
    }
}

CONTROLLER

#[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
            );
        }
    }
}

SECURITY.YAML

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: { service: google }

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.

@ManoloTonto1
Copy link

Getting the same thing here boss
Invalid state parameter passed in callback URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants