Giter Site home page Giter Site logo

netzbegruenung / keycloak-mfa-plugins Goto Github PK

View Code? Open in Web Editor NEW
30.0 30.0 6.0 529 KB

Keycloak Authentication Provider implementation to get a 2nd-factor authentication with a OTP/code/token send via SMS

License: Apache License 2.0

Java 89.72% FreeMarker 6.11% JavaScript 4.18%

keycloak-mfa-plugins's People

Contributors

dasniko avatar happyler avatar melegiul avatar nikohadouken avatar svenseeberg avatar verhde avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

keycloak-mfa-plugins's Issues

Editing Users in Keycloak 20.x broken

When editing a user in the admin interface, an error 500 appears with the following stack trace:

2022-11-04 09:31:25,817 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-57) Uncaught server error: java.lang.AbstractMethodError: Receiver class netzbegruenung.keycloak.authenticator.SmsMobileNumberProvider does not define or inherit an implementation of the resolved method 'abstract java.util.stream.Stream getDisableableCredentialTypesStream(org.keycloak.models.RealmModel, org.keycloak.models.UserModel)' of interface org.keycloak.credential.CredentialInputUpdater.
	at org.keycloak.credential.LegacyUserCredentialManager.lambda$getDisableableCredentialTypesStream$5(LegacyUserCredentialManager.java:185)
	at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:271)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
	at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1693)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
	at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
	at org.keycloak.models.utils.ModelToRepresentation.toRepresentation(ModelToRepresentation.java:265)
	at org.keycloak.services.resources.admin.UserResource.getUser(UserResource.java:286)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:183)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:183)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:183)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
	at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:82)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:42)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
	at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:84)
	at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:71)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$6.handle(VertxHttpRecorder.java:430)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$6.handle(VertxHttpRecorder.java:408)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
	at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
	at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:564)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:829)

.jar Supporting Keycloak 24.0.1

Keycloak launch a new major version containing the full implementation of user profile and progressive profiling.

Could you generate a new jar support this version?

Enable aborting SMS 2FA setup

When a user enters a phone number and clicks on next, the phone number must be confirmed by entering the SMS code. Until the right SMS code is submitted, a new required action "Set phone number" is added to the user, even if other 2FA methods are configured.

This makes it hard for the user to abort the setup. The only way currently is to successfully setup SMS 2FA.

Landline Regex Setting

Add a config option for recognizing land lines. If a land line is used, add spaces between each character of the SMS code. This makes it easier to use in cases where the SMS is automatically being read to the user.

Fix Keycloak errors caused by SMS 2FA

With this plugin enabled, we experience an increased rate of Keycloak errors. We need to investigate if this is caused by Keycloak itself or this plugin.

There are 2 errors that seem to be related to activating the "Force 2FA" function:

  • keycloak/keycloak#15656
  • LDAP Federation Provider sync errors (2022-11-28 14:01:03,783 WARN [org.keycloak.storage.ldap.LDAPStorageProviderFactory] (Timer-0) User with ID 'XXX' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider 'myldap') It seems that this is unrelated.

The error rate is strongly increasing with the feature activated. But it seems there are other causes as well. We should try to answer the following questions:

  • Are there other parts in the SMS 2FA plugin that trigger these errors?
  • Is the SMS 2FA plugin using Keycloak classes/methods incorrectly or are these actually Keycloak errors?

Prohibit sending forms multiple times

It is possible for users to hit the submit button multiple times when entering a code. This results in an error page for users, because usually for the second request the CSRF token has expired.

We should disable the submit action after it has been triggered once.

Change credential priority

It seems that credentials can have priorities. As TOTP and FIDO2 should always be preferred to SMS, we should try to save the credential with a lesser priority than the other methods. Not sure if this is possible.

Support keycloak 21

keycloak 21 breaks SMS plugin:

2023-02-24 08:38:32,255 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-5) Uncaught server error: java.lang.NoSuchMethodError: 'org.jboss.resteasy.spi.HttpRequest org.keycloak.authentication.AuthenticationFlowContext.getHttpRequest()'
	at netzbegruenung.keycloak.authenticator.SmsAuthenticator.action(SmsAuthenticator.java:97)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:154)
	at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:985)
	at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:324)
	at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:295)
	at org.keycloak.services.resources.LoginActionsService.authenticateInternal(LoginActionsService.java:287)
	at org.keycloak.services.resources.LoginActionsService.access$100(LoginActionsService.java:112)
	at org.keycloak.services.resources.LoginActionsService$1.runInternal(LoginActionsService.java:267)
	at org.keycloak.common.util.ResponseSessionTask.run(ResponseSessionTask.java:67)
	at org.keycloak.common.util.ResponseSessionTask.run(ResponseSessionTask.java:44)
	at org.keycloak.models.utils.KeycloakModelUtils.runJobInRetriableTransaction(KeycloakModelUtils.java:299)
	at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:260)
	at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:352)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
	at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:82)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:42)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
	at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:84)
	at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:71)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$6.handle(VertxHttpRecorder.java:430)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$6.handle(VertxHttpRecorder.java:408)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
	at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
	at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:829)

Error when adding an execution to reset credentials flow

When adding a SMS authenticator action to the reset credentials, an error is thrown:

2022-09-05 12:27:43,293 WARN  [org.keycloak.services] (executor-thread-565) KC-SERVICES0013: Failed authentication: java.lang.NullPointerException
        at netzbegruenung.keycloak.authenticator.SmsAuthenticator.authenticate(SmsAuthenticator.java:71)
        at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:460)
        at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:136)
        at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:990)
        at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:321)
        at org.keycloak.services.resources.LoginActionsService.processResetCredentials(LoginActionsService.java:660)
        at org.keycloak.services.resources.LoginActionsService.resetCredentials(LoginActionsService.java:450)
        at org.keycloak.services.resources.LoginActionsService.resetCredentialsPOST(LoginActionsService.java:366)
        at jdk.internal.reflect.GeneratedMethodAccessor694.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)

The context.getAuthenticatorConfig() probably returns null.

SMS 2FA with SMSEagle Gayteway Device

Try using the 2FA SMS in conjunction with a local hardware SMSEagle device. And actually I have gotten countless webhooks or SMSEagle API applications to run with the device.

But somehow I don't understand the Keycloak error message.

`2024-01-26 11:54:26,048 INFO [netzbegruenung.keycloak.authenticator.gateway.SmsServiceFactory] (executor-thread-1) API request: (POST https://mySMSEagleURL/api/v2/messages/sms) 400

2024-01-26 11:54:26,048 ERROR [netzbegruenung.keycloak.authenticator.gateway.SmsServiceFactory] (executor-thread-1) Failed to send message to 0157xxxxxxxx with answer: {"message":{"to":"The type of the attribute must be one of array (string given)."}}. Validate your config.
`

Allow disabling SMS 2FA

It should be possible to deactivate the SMS 2FA method with the settings introduced in #6 . The easiest way to achieve this is by removing the phone number.

Implement flexible API

Support configuration for multiple SMS provider APIs. It should be possible to select between URL encoded parameters and JSON body. And it should be possible map the 4 attributes username, access token, receiver number and message text to 4 arbitrary JSON or urlencoded variables.

This should be added to the settings of each flow (https://github.com/netzbegruenung/keycloak-2fa-sms-authenticator/blob/main/src/main/java/netzbegruenung/keycloak/authenticator/SmsAuthenticatorFactory.java#L51) to allow different providers in different flows and realms.

For example https://www.twilio.com/docs/sms/api should work. https://www.gtx-messaging.com/de/api-docs/sms-rest-api/ as well.

Set Input Field Type for SMS OTP

It would be nice to have the input type for the text field for the SMS OTP set to numbers, so mobile users can type in the code easier.

Improve phone number replacement rules

Sometimes users are not able to correctly enter their country code. They enter a zero (indicating the provider prefix) between the county code and provider number (for example +490176 ... instead of +49176 .... At least for Germany, these can be safely replaced (+490X > +49X).

Also, if a number starts with the local country prefix and a + is forgotten, the + should be added automatically.

App Auth: Replace HTTP Signature and consistency check

See #63 (comment)

Most of it is already done. But still the HTTP Signatures replacement and the consistency check on setup (e.g. on setup a signature is sent and checked with the supplied public key) is open.

Open Issues

  • Replace HTTP Signatures with verification of client-side generated JWT
  • Verify code challenge on Action-Token-endpoint (authenticate) from the code_verifier query parameter

Todo

  • Delete login attempt/challenge after Action-Token-endpoint (for authenticate) was succesful called
  • Implement auto refresh of the Setup/Authenticate Keycloak page via Server Sent Events
  • Response payload of Get-Challenges-endpoint returns a list of challenges instead of a single one
  • Update Error Responses
  • Get Challenges and Reply Challenge Endpoint need to return the 409 error also when there is no login attempt. Otherweise the client does'nt know if the authenticator needs to get removed.
  • Remove OpenAPI spec
  • Rework documentation (readme)
  • Return clientName and clientUrl in payload of Get-Challenges-endpoint
  • Consistency check on setup
  • Reduce expiration for the app-auth-action-token to 60 seconds
  • Make expiration for app-auth-action-token configurable Admin UI
  • Rename attribute secret in Challenge DTO to codeChallenge
  • Remove device_id query parameter on Get-Challenges-endpoint retrieve it from the signature

Allow message customization

Enable customization of the SMS text. We probably need some sort of placeholder for the code itself. Then we can add a setting for a custom text.

Fix SMS API

The mobile number is not read from the credential store while sending the SMS.

Enforce 2FA only if no other action is set

When no 2FA method is activated for a user, we have an option to force a user to set up SMS 2FA. However, this is also triggered when a user already has the "Configure OTP" or "Webauthn Register" required action.

We should check if a user already has any other 2FA configuration required action set before setting the SMS 2FA action in the enforcement mode. I'm not sure if we can check for unknown 2FA methods, but at least the 2 existing ones should be recognized.

Show last 3 phone number digits in Account Security

It would be good for account owners to recognize which phone number is currently stored. This could be indicated by showing the last 3 digits of the phone number like ****123, right behind "Phone Number". This would be the same location as the name of the Webauthn device:
image

Maybe just set the User Label?!

App Auth: Server Sent Events EP throws ContextNotActiveException

After upgrading from Keycloak 23.0.7 to 24.0.2, the Resource Provider for server sent events throws exception below. During my tests this was not critical. The form submission, which is triggered by the server sent event, still works as expected.

const source = new EventSource("${appAuthStatusUrl?no_esc}");

Nonetheless we should check the current quarkus version and changelog for hints.

2024-03-27 07:54:34,304 ERROR [io.quarkus.vertx.http.runtime.QuarkusErrorHandler] (executor-thread-10) HTTP Request to
/realms/realm/app-auth-status?session_code=sfa7st4CvMBni23bpkTBIrtKeUJf5zV41sIARpz6BQ0&execution=app-register&client_id=account-console&tab_id=bNhbEzFwlrw failed,
error id: 2ec0baaa-0557-4ac2-8aa6-ecf2a6882d62-2:
jakarta.enterprise.context.ContextNotActiveException:
RequestScoped context was not active when trying to obtain a bean instance for a client proxy of CLASS bean
[class=io.quarkus.vertx.http.runtime.CurrentVertxRequest, id=0_6n6EmChCiiDdd8HelptG_A0AE]
	- you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding
	at io.quarkus.arc.impl.ClientProxies.notActive(ClientProxies.java:70)
	at io.quarkus.arc.impl.ClientProxies.getSingleContextDelegate(ClientProxies.java:30)
	at io.quarkus.vertx.http.runtime.CurrentVertxRequest_ClientProxy.arc$delegate(Unknown Source)
	at io.quarkus.vertx.http.runtime.CurrentVertxRequest_ClientProxy.getCurrent(Unknown Source)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.keycloak.quarkus.runtime.integration.resteasy.ResteasyVertxProvider.getContextData(ResteasyVertxProvider.java:44)
	at org.keycloak.common.util.Resteasy.getContextData(Resteasy.java:62)
	at org.keycloak.services.error.KeycloakErrorHandler.toResponse(KeycloakErrorHandler.java:54)
	at org.jboss.resteasy.reactive.server.core.RuntimeExceptionMapper.mapException(RuntimeExceptionMapper.java:100)
	at org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.mapExceptionIfPresent(ResteasyReactiveRequestContext.java:346)
	at org.jboss.resteasy.reactive.server.handlers.ExceptionHandler.handle(ExceptionHandler.java:15)
	at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:150)
	at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
	at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:840)

SMS 2FA plugin stuck in simulation mode

During testing I somehow managed to get my plugin stuck in "simulation" mode. I've gone as far as to destroy all custom flows and deleting the sms step. Saving. Coming back and readding the flow and ensuring simulation toggle is off. However when client initiates SMS flow, my console still shows **** SIMULATION MODE *** and I don't know how to turn it off now.

Introduce verification step

A phone number should be verified before the user should be able to use it as second factor. This is similar to TOTP, where the user needs to enter the first OTP, before TOTP becomes active as a second factor.

Procedure:

  • User enters a phone number
  • SMS with verification code is sent to number
  • User enters code
  • SMS 2FA is activated

Improve loading configs

In the moment the custom required actions assume there exists an authenticator config object with the alias "sms-2fa".
Instead of loading the config by this predefined alias it should be loaded either from the database (with arbitrary alias) or loaded from keycloaks globale settings (see #20 (comment) and following)

Update Number required action fails

With versions 0.2.1 and 0.2.2 the update phone number required action fails after entering the phone number. No code is being sent and a mail notification about a failed login attempt is being sent to the user.

Fix license headers

Currently there is a mix of licenses. We need to check which files actually contain larger parts of original code and probably should keep the corresponding license.

Show existing phone number in Action

If the Phone Number Action is shown during log in, it does not yet show existing phone numbers. It would be better for users, if the existing phone number is shown.

Add fallback 2FA setting

Add a boolean setting that automatically adds a required action for registering a phone number, if no other second factor is configured for the user.

App Authenticator implementation details

Challenge Endpoint (Setup App Auth):

/realms/realm-id/login-actions/action-token?key=jwt&client_id=account-console&tab_id=someTabId

Setup Steps:

  1. The URL is transmitted to the device by QR-Code and contains a one-time JWT (in case the device is not authenticated).
  2. The mobile device is supposed to use this URL to transmit its device data.

Required additional query parameters:

device_id: firebase device's registration token
device_os: OS
public_key: base64 encoded public key (RSA)

Authentication Endpoint (basically the same endpoint):

/realms/realm-id/login-actions/action-token?key=jwt&client_id=account-console&tab_id=someTabId

Auth Steps:

  1. The URL + JWT is transmitted to the device by firebase. (In the backend both endpoint only differ in the supplied JWT and their token handler. The setup token and auth token both grant access to a single unique server side action)
  2. The mobile device is supposed to use this URL to solve the supplied challenge (see below).

Required additional query parameters:

secret: decrypted secret (see below)

The firebase message contains an encrypted secret (RSA), which the app is supposed to decrypt with their secret key and send the decrypted string back to keycloak for verification.

Open issues

How to make sure that the apps public key was not tampered during transmission. (solved, see: https://chatbegruenung.de/group/gruene-app-alle?msg=gYqrCfqTgunZexoEA)

Not able to migrate users and use the SMS (will help to complete future pull request with config guide)

Hey guys, first of all, thanks for keep the implementation and share this repository.

I'm working in a project that needs to replace the current Auth Service for Keycloak with SMS as MFA.

I'm trying to use your plugin, but even watching the Niko Kobler videos and reading the repository README. i'm facing a lot of doubts and problems to implement it.

Thinking on that, i'm forking the repository to update the README with all the instructions to implement it, and help eveyone that needs to have it in their Keycloak.

I'll start adding here some questions and explaining my steps.

Configuration Guide - Part 1

  • I have the Keycloak running in a Docker Container (i'll add the steps to install it in the ReadMe too)

  • To be able to use the plugin, you need to have an User Attibute called 'mobile_number' to store the phone numbers. This can be done using the User profile or using User Attributes
    image
    I've the User Profile Attirbute set as mobile_number, but in the docker logs, looks like the user number is not identified
    image

  • Go to Authentication > Flows > Duplicate the 'browser' flow - I've set the name as 'browser with sms'
    image
    image

  • On 'browser with sms forms' step, click over the + and add a new step
    image

  • Search by 'SMS Authentication (2FA)' and click on Add button
    image

  • Drag and Drop the added step to under the Useranme Password Form
    image

  • Change the to 'Required' and click over the engine to Edit the plugin settings
    image

  • Set the alias as 'sms-2fa' and click save
    image

  • Go back to Authentication Flows page, find the create flow, click over the settings and 'Bind Flow' option
    image
    image

  • This is the basic configuration to allow you to test before configure you SMS Api Service.

Errors with the configuration above

  • When i'm creating a new user (using the keycloak sign-up form), the SMS Code step don't appear. The user is created, but any SMS is sent.
  • When i tried to sign-in using the new user created, i wasn't able, and got the following error logged in the docker logs
    2024-03-09 10:22:28 2024-03-09 13:22:28,935 WARN [org.keycloak.services] (executor-thread-337) KC-SERVICES0013: Failed authentication: org.keycloak.authentication.AuthenticationFlowException: authenticator: mobile-number-authenticator 2024-03-09 10:22:28 at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:432) 2024-03-09 10:22:28 at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:249) 2024-03-09 10:22:28 at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:380) 2024-03-09 10:22:28 at org.keycloak.authentication.DefaultAuthenticationFlow.continueAuthenticationAfterSuccessfulAction(DefaultAuthenticationFlow.java:181) 2024-03-09 10:22:28 at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:157) 2024-03-09 10:22:28 at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:988) 2024-03-09 10:22:28 at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:362) 2024-03-09 10:22:28 at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:333) 2024-03-09 10:22:28 at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:325) 2024-03-09 10:22:28 at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:390) 2024-03-09 10:22:28 at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$authenticateForm_32b8e198ac3110abd1d5774e83a4cf87858129f4.invoke(Unknown Source) 2024-03-09 10:22:28 at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29) 2024-03-09 10:22:28 at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141) 2024-03-09 10:22:28 at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145) 2024-03-09 10:22:28 at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576) 2024-03-09 10:22:28 at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513) 2024-03-09 10:22:28 at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538) 2024-03-09 10:22:28 at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29) 2024-03-09 10:22:28 at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29) 2024-03-09 10:22:28 at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) 2024-03-09 10:22:28 at java.base/java.lang.Thread.run(Thread.java:840)

What i did to fix the settings

  • Open the Realm Authentication page, under the Required Actions, turned on the 'Phone Validation' option
    image

  • With that, after create a new user, the SMS Code page starts to appear
    image
    image

  • And i've got this in the docker logs
    image

  • Now, FOR NEW USERS i'm able to use the SMS CODE.

More questions with this basic settings

I've noticed that the created user got the following credentials
image

I think, that was that allow the user login using the SMS 2FA.

But as mentionated at the very beggining, i'm migrating existent users to the Keycloak using the Keycloak Rest Api.

Tried to add this credential throw the api, and i'm getting this errror:
image

How can i add the Mobile-number credential to the migrated users and allow them use the SMS Code flow?

Because if i follow the above steps and don't add the credential, when i trying to login, i'm getting the following error:

2024-03-09 10:48:25 2024-03-09 13:48:25,741 WARN [org.keycloak.services] (executor-thread-358) KC-SERVICES0013: Failed authentication: org.keycloak.authentication.AuthenticationFlowException: authenticator: mobile-number-authenticator 2024-03-09 10:48:25 at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:432) 2024-03-09 10:48:25 at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:249) 2024-03-09 10:48:25 at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:380) 2024-03-09 10:48:25 at org.keycloak.authentication.DefaultAuthenticationFlow.continueAuthenticationAfterSuccessfulAction(DefaultAuthenticationFlow.java:181) 2024-03-09 10:48:25 at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:157) 2024-03-09 10:48:25 at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:988) 2024-03-09 10:48:25 at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:362) 2024-03-09 10:48:25 at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:333) 2024-03-09 10:48:25 at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:325) 2024-03-09 10:48:25 at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:390) 2024-03-09 10:48:25 at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$authenticateForm_32b8e198ac3110abd1d5774e83a4cf87858129f4.invoke(Unknown Source) 2024-03-09 10:48:25 at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29) 2024-03-09 10:48:25 at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141) 2024-03-09 10:48:25 at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145) 2024-03-09 10:48:25 at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576) 2024-03-09 10:48:25 at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513) 2024-03-09 10:48:25 at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538) 2024-03-09 10:48:25 at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29) 2024-03-09 10:48:25 at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29) 2024-03-09 10:48:25 at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) 2024-03-09 10:48:25 at java.base/java.lang.Thread.run(Thread.java:840)

Thanks in advanced, I know it's a lot of information, but i really need some help here to implement it.

Put 6 digit code first

https://github.com/netzbegruenung/keycloak-2fa-sms-authenticator/blob/b18604f29a254d0d5a376cac99f9fa4defed916b/src/main/resources/theme-resources/messages/messages_de.properties#L1

Message previews allow many users to just type the 6 digit code from their phone from message preview, whereas right now the digit code is to late in the message text and not shown in preview anymore:

https://helpdesk.verdigado.com/#ticket/zoom/80807/122860

grafik

Neuerdings wird auch von Apple der Code nicht mehr erkannt melden Anwender*innen im chat:

Früher:

Ihr Grünes Netz SMS Code lautet 738269 und ist gültig für 15 Minuten.

Jetzt:

Ihr Grünes Netz SMS ist 961640 und ist gültig für 15 Minuten.

https://chatbegruenung.de/channel/chatbegruenung?msg=XBbNPWeLHZwpJNhm5

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.