Search code examples
keycloakkeycloak-rest-api

How to integrate keycloak sms authentication by API?


I have a keycloak server and Laravel application using custom KeycloakProvider:

public function loginByEmail(string $email, string $password): SsoTokens
{
    try {
        $data = $this->realmEndpoint->makeRequest(
            HttpClientProvider::METHOD_POST,
            self::KEYCLOAK_AUTH_URL,
            [
                'client_id' => config('services.keycloak.realm_client'),
                'client_secret' => config('services.keycloak.realm_secret'),
                'grant_type' => 'password',
                'username' => $email,
                'password' => $password,
                'scope' => 'openid'
            ]
        );
    } catch (TransportUnauthorizedException $e) {
        throw new UnauthorizedException($e);
    } catch (HttpClientException $e) {
        throw new TransportException($e);
    }

    return $this->extractTokens($data);
}

Now my goal is to set up basic SMS authentication by the user's mobile phone number. I found some tools (1, 2), but they don't provide API, just HTML pages. Is there a solution?


Solution

  • I found a solution. To login without knowing someone's password:

    1. Send SMS;
    2. Confirm phone number by a code;
    3. Get the keycloak ID of a target user;
    4. Log in as a user having the right to impersonate;
    5. Exchange tokens with a target user.

    TOKEN_EXCHANGE keycloak feature required.

    Steps 1-3 I implemented with Laravel, steps 4-5 with Keycloak APIs:

    public function loginByUserId(string $userId): SsoTokens
        {
            try {
                $impersonatorData = $this->realmEndpoint->makeRequest(
                    HttpClientProvider::METHOD_POST,
                    self::KEYCLOAK_AUTH_URL,
                    [
                        'client_id' => config('services.keycloak.realm_client'),
                        'client_secret' => config('services.keycloak.realm_secret'),
                        'grant_type' => 'password',
                        'username' => config('services.keycloak.admin_username'),
                        'password' => config('services.keycloak.admin_password'),
                        'scope' => 'openid',
                    ]
                );
    
                $data = $this->realmEndpoint->makeRequest(
                    HttpClientProvider::METHOD_POST,
                    self::KEYCLOAK_AUTH_URL,
                    [
                        'client_id' => config('services.keycloak.realm_client'),
                        'client_secret' => config('services.keycloak.realm_secret'),
                        'grant_type' => 'urn:ietf:params:oauth:grant-type:token-exchange',
                        'requested_subject' => $userId,
                        'subject_token' => $impersonatorData['access_token'],
                        'scope' => 'openid',
                    ]
                );
            } catch (TransportUnauthorizedException $e) {
                throw new UnauthorizedException($e);
            } catch (HttpClientException $e) {
                throw new TransportException($e);
            }
    
            return $this->extractTokens($data);
        }