FX-HAO / keycloak-phone-authenticator

Obtain token and reset password via SMS
129 stars 44 forks source link

Use Phone Authenticator Form For User Authentication And Registration #9

Open dev-aakash1998 opened 3 years ago

dev-aakash1998 commented 3 years ago

Hi, apologies for my vague title but there are a couple of problem statements that I'm trying to solve here. I have no coding experience in Java, so please bear with me if they are quite elementary. I have already deployed this SPI and was wondering how to proceed with handling specific use cases. My Use case is this:

Get User's Phone Number and verify with OTP, if no users are mapped for the phoneNumber, create new user, ask for the user details in the login form itself. I have done some basic additions to your code in src/main/com/hfx/keycloak/spi/UsernamePasswordorPhoneForm

public boolean validateVerificationCode(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
        context.clearUser();
        UserModel user = this.getUser(context, formData);
        String phoneNumber = (String)formData.getFirst("phoneNumber");
        String groupName = "/is_externaluser";
        GroupModel external_group = KeycloakModelUtils.findGroupByPath(context.getRealm(), groupName);
        if (user == null && VerificationCode.verify(context, VERIFICATION_CODE_KIND)){
            user = context.getSession().users().getUserByUsername(context.getRealm(), phoneNumber);
            if (user == null){
                user = context.getSession().users().addUser(context.getRealm(), phoneNumber);
                user.setEnabled(true);
                log.info(String.format("Created New User with Username %s", phoneNumber));
                user.joinGroup(external_group);
                log.info("Added User To External Group");
                List<String> values = new ArrayList<String>();
                values.add(phoneNumber);
                user.setAttribute("contact_number", values);
                NEW_USER_CREATED = true;
            }
            else{
                user = null;
            }
        }
        return user != null && VerificationCode.verify(context, VERIFICATION_CODE_KIND) && this.validateUser(context, user, formData);
    }

But I want to emit the value of NEW_USER_CREATED to the frontend so that either

  1. The Login Card Changes To The Registration Form within the same domain itself, taking in User Details like fullname, email, etc
  2. Redirect To The Custom Account Page provided by you

But I don't have enough understanding about this implementation to go ahead with either. If you have any ideas please let me know.

Also, If I wanted to do away with the SMS Interface/SPI Packages (keycloak-phone-authenticator-yuntongxun-sms and yuntongxun4j) and simply wanted to add the SMS implementation in the main package keycloak-phone-authenticator how would I go about it?

I know this is not an 'issue' in the package itself but I have searched through the internet and have found no resource as good as yours for my usecase hence was wondering if I could pick your brains about it.

Thanks in advance!

FX-HAO commented 3 years ago

I think you can forward users to the registration page after login using SMS. the code is like this:

Response challenge = context.form()
        .setAttribute("phoneNumber", phoneNumber)
        .createResponse(UserModel.RequiredAction.REGISTER);
context.challenge(challenge);

but the problem is, the phoneNumber would be lost, because the registration page doesn't have an attribute to store it and post it back to class RegistrationUserCreation. but it's feasible if you don't mind to a little bit more code.

The idea is, you need to create a new template file, which is almost the same as register.ftl(just copy it). but you add a hidden input to store the attribute phoneNumber. then you need to add a new class that extends to RegistrationUserCreation. you need to overwrite the function success(https://github.com/keycloak/keycloak/blob/f154b0b20993aac9d5b4d30f255236aa0e3e788a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java#L113). Read phoneNumber from the request form and set it to the user.

BTW, I'm also not much familiar with Keycloak (since I left the company and I personally don't use it ). maybe there's a better solution.

==================== If you want to get rid of the SMS implementation libraries, just create a class extending SmsService. Basically you just move the code in SMS implementation into this library.

dev-aakash1998 commented 3 years ago

For Deploying The phone-authenticator package without The SPI Packages I did the following:

The Content Being: com.hfx.keycloak.spi.impl.SmsServiceProviderFactoryImpl

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

/subsystem=keycloak-server:list-add(name=providers,value=module:com.hfx.keycloak-phone-authenticator)

/subsystem=keycloak-server/theme=defaults:write-attribute(name=modules,value=[com.hfx.keycloak-phone-authenticator])

run-batch
stop-embedded-server

embed-server --server-config=standalone.xml --std-out=echo
batch

/subsystem=keycloak-server:list-add(name=providers,value=module:com.hfx.keycloak-phone-authenticator)

/subsystem=keycloak-server/theme=defaults:write-attribute(name=modules,value=[com.hfx.keycloak-phone-authenticator])

run-batch
stop-embedded-server
module add --name=com.hfx.keycloak-phone-authenticator --resources=keycloak-phone-authenticator-1.0.0-SNAPSHOT.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-common,org.hibernate,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.jboss.logging,javax.api,javax.ws.rs.api,javax.transaction.api,javax.persistence.api,org.jboss.resteasy.resteasy-jaxrs,org.apache.httpcomponents,org.apache.commons.lang,com.squareup.okhttp3,javax.xml.bind.api,com.google.code.gson

I feel like I'm missing something elementary here, please correct me if any of these steps were wrong. Thanks In Advance!

FX-HAO commented 3 years ago

It looks good to me. But the error seems like SmsServiceProviderFactoryImpl was registered in com.hfx.keycloak.spi.impl.SmsServiceProviderFactory but was not found. Maybe you need to check the package of SmsServiceProviderFactoryImpl and see if it's correct?

some tips about how to test your code efficiently:

you can clone the Keycloak repository and put your code under folder examples. Then you need to add this line.

# in examples/pom.xml

...
<module>keycloak-phone-authenticator</module>
...
# in testsuite/utils/pom.xml

        ...
        <dependency>
            <groupId>com.hfx</groupId>
            <artifactId>keycloak-phone-authenticator</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        ...

Then you can run keycloak locally using the following (refer):

mvn exec:java -Pkeycloak-server -f testsuite/utils/pom.xml -Dkeycloak.connectionsJpa.url=jdbc:postgresql://localhost/postgres -Dkeycloak.connectionsJpa.driver=org.postgresql.Driver -Dkeycloak.connectionsJpa.user=postgres -Dkeycloak.connectionsJpa.password=postgres -Darquillian.debug=true -Dmaven.surefire.debug=true -Dkeycloak.logging.level=debug -Dkeycloak.infinispan.logging.level=debug -Dauth.server.debug=true

BTW, don't forget to run mvn clean install and restart keycloak whenever you change your code