Keycloak Custom SPI Authenticator - KC-SERVICES0013: Failed authentication - keycloak

Keycloak version 18
Purpose: I want to store scope parameters in user session notes to be later fetched in my custom protocol mapper. To do the same I am referring to this blog post which talks about creating a custom JS authenticator, however, as I prefer Java, I tried implementing the same in java.
I can see the custom authenticator, in my keycloak server-info and during setup.
Here is my implementation:
public class MyAuthenticator implements Authenticator {
private static final Logger log = LoggerFactory.getLogger(MyAuthenticator.class);
#Override
public void authenticate(AuthenticationFlowContext context) {
log.info("authenticate :: START");
try {
AuthenticationSessionModel authenticationSession = context.getAuthenticationSession();
String sessionClientNote = authenticationSession.getClientNote("scope");
log.info("client note from auth session : " + sessionClientNote);
authenticationSession.setUserSessionNote("scope", sessionClientNote);
context.success();
} catch (Exception e) {
context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR,
context.form().setError("smsAuthSmsNotSent", e.getMessage())
.createErrorPage(Response.Status.INTERNAL_SERVER_ERROR));
}
}
#Override
public void action(AuthenticationFlowContext context) {
log.info("No Custom Action is set.");
context.success();
}
#Override
public boolean requiresUser() {
return true;
}
#Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
#Override
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
log.info("No required actions are set.");
}
#Override
public void close() {
}
}
public class MyAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "my-authenticator";
#Override
public String getId() {
return PROVIDER_ID;
}
#Override
public String getDisplayType() {
return "MY Authentication";
}
#Override
public String getHelpText() {
return "Will read the scope parameters and store in user session";
}
#Override
public String getReferenceCategory() {
return PasswordCredentialModel.TYPE;
}
#Override
public boolean isConfigurable() {
return true;
}
#Override
public boolean isUserSetupAllowed() {
return true;
}
#Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
#Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
#Override
public Authenticator create(KeycloakSession session) {
return new MyAuthenticator();
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public void close() {
}
}
Here is how the authenticator is set up as the last step as execution :
However, I get this exception while trying to log in or test this via postman. This is not even hitting the custom code that I have written but failing much before that?
2022-08-01 10:52:21,721 WARN [org.keycloak.authentication.DefaultAuthenticationFlow] (executor-thread-25) REQUIRED and ALTERNATIVE elements at same level! Those alternative executions will be ignored: [auth-cookie, identity-provider-redirector, null]
2022-08-01 10:52:21,721 WARN [org.keycloak.services] (executor-thread-25) KC-SERVICES0013: Failed authentication: org.keycloak.authentication.AuthenticationFlowException: authenticator: my-authenticator
at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:437)
at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:264)
at org.keycloak.authentication.AuthenticationProcessor.authenticateOnly(AuthenticationProcessor.java:1030)
at org.keycloak.authentication.AuthenticationProcessor.authenticate(AuthenticationProcessor.java:892)
And this is the line number. I don't understand this code and what is it trying to do? My is simple authorization code pattern. Can someone please advice/review my code?
I have also set these flows at client level.

Related

KEYCLOAK - Extending OIDC Protocol | Missing Credentials Tab | Add extra claims in AccessTokenResponse

We are trying to implement SMART On FHIR healthcare authorization protocol specification. This spec is an extension to OIDC (open id connect protocol). In SMART on FHIR, we need to add extra claims called 'patient' with value say '123' in AccessTokenResponse object during the OAUTH dance.
In order to accomplish this, I tried to extended the OIDCLoginProtocol and OIDCLoginProtocolFactory classes and given a new name to this protocol called 'smart-openid-connect'. I created this as a SPI (service provider interface) JAR and copied it to /standalone/deployments folder. Now, I can see the new protocol called 'smart-openid-connect' in the UI, but it does not show Access Type options in the client creation screen to select as a confidential client. Hence, I am not able to create client secrets as the Credentials menu is not appearing for this new protocol.
I have the following questions:
How to enable the Credentials tab in the client creation screen using SPI for the new protocol that I created.?
Which class I need to override to add extra claims in AccessTokenResponse ?
Kindly help me in this regard.
Thanks for your help in advance.
I have followed your steps for developing our custom protocol. When we migrate our company existed authentication protocol, I have used org.keycloak.adapters.authentication.ClientCredentialsProvider, org.keycloak.authentication.ClientAuthenticatorFactory, org.keycloak.authentication.ClientAuthenticator classes for defining our custom protocol. Credentials tab is only visible if oidc and confidential choices are selected. It is defined on UI javascript codes. So we choose oidc option for setting custom protocol. Afterwards, we return back to our custom protocol.
XyzClientAuthenticatorFactory
public class XyzClientAuthenticatorFactory implements ClientAuthenticatorFactory, ClientAuthenticator {
public static final String PROVIDER_ID = "xyz-client-authenticator";
public static final String DISPLAY_TEXT = "Xyz Client Authenticator";
public static final String REFERENCE_CATEGORY = null;
public static final String HELP_TEXT = null;
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED};
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(Constants.CLIENT_SETTINGS_APP_ID);
property.setLabel("Xyz App Id");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(Constants.CLIENT_SETTINGS_APP_KEY);
property.setLabel("Xyz App Key");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
}
#Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
}
#Override
public String getDisplayType() {
return DISPLAY_TEXT;
}
#Override
public String getReferenceCategory() {
return REFERENCE_CATEGORY;
}
#Override
public ClientAuthenticator create() {
return this;
}
#Override
public boolean isConfigurable() {
return false;
}
#Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
#Override
public boolean isUserSetupAllowed() {
return false;
}
#Override
public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
return configProperties;
}
#Override
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
Map<String, Object> result = new HashMap<>();
result.put(Constants.CLIENT_SETTINGS_APP_ID, client.getAttribute(Constants.CLIENT_SETTINGS_APP_ID));
result.put(Constants.CLIENT_SETTINGS_APP_KEY, client.getAttribute(Constants.CLIENT_SETTINGS_APP_KEY));
return result;
}
#Override
public Set<String> getProtocolAuthenticatorMethods(String loginProtocol) {
if (loginProtocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
Set<String> results = new LinkedHashSet<>();
results.add(Constants.CLIENT_SETTINGS_APP_ID);
results.add(Constants.CLIENT_SETTINGS_APP_KEY);
return results;
} else {
return Collections.emptySet();
}
}
#Override
public String getHelpText() {
return HELP_TEXT;
}
#Override
public List<ProviderConfigProperty> getConfigProperties() {
return new LinkedList<>();
}
#Override
public ClientAuthenticator create(KeycloakSession session) {
return this;
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public void close() {
}
#Override
public String getId() {
return PROVIDER_ID;
}
}
XyzClientCredential
public class XyzClientCredential implements ClientCredentialsProvider {
public static final String PROVIDER_ID = "xyz-client-credential";
#Override
public String getId() {
return PROVIDER_ID;
}
#Override
public void init(KeycloakDeployment deployment, Object config) {
}
#Override
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
}
}
XyzLoginProtocolFactory
public class XyzLoginProtocolFactory implements LoginProtocolFactory {
static {
}
#Override
public Map<String, ProtocolMapperModel> getBuiltinMappers() {
return new HashMap<>();
}
#Override
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
return new XyzLoginProtocolService(realm, event);
}
protected void addDefaultClientScopes(RealmModel realm, ClientModel newClient) {
addDefaultClientScopes(realm, Arrays.asList(newClient));
}
protected void addDefaultClientScopes(RealmModel realm, List<ClientModel> newClients) {
Set<ClientScopeModel> defaultClientScopes = realm.getDefaultClientScopes(true).stream()
.filter(clientScope -> getId().equals(clientScope.getProtocol()))
.collect(Collectors.toSet());
for (ClientModel newClient : newClients) {
for (ClientScopeModel defaultClientScopeModel : defaultClientScopes) {
newClient.addClientScope(defaultClientScopeModel, true);
}
}
Set<ClientScopeModel> nonDefaultClientScopes = realm.getDefaultClientScopes(false).stream()
.filter(clientScope -> getId().equals(clientScope.getProtocol()))
.collect(Collectors.toSet());
for (ClientModel newClient : newClients) {
for (ClientScopeModel nonDefaultClientScope : nonDefaultClientScopes) {
newClient.addClientScope(nonDefaultClientScope, true);
}
}
}
#Override
public void createDefaultClientScopes(RealmModel newRealm, boolean addScopesToExistingClients) {
// Create default client scopes for realm built-in clients too
if (addScopesToExistingClients) {
addDefaultClientScopes(newRealm, newRealm.getClients());
}
}
#Override
public void setupClientDefaults(ClientRepresentation rep, ClientModel newClient) {
}
#Override
public LoginProtocol create(KeycloakSession session) {
return new XyzLoginProtocol().setSession(session);
}
#Override
public void init(Config.Scope config) {
log.infof("XyzLoginProtocolFactory init");
}
#Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(event -> {
if (event instanceof RealmModel.ClientCreationEvent) {
ClientModel client = ((RealmModel.ClientCreationEvent)event).getCreatedClient();
addDefaultClientScopes(client.getRealm(), client);
addDefaults(client);
}
});
}
protected void addDefaults(ClientModel client) {
}
#Override
public void close() {
}
#Override
public String getId() {
return XyzLoginProtocol.LOGIN_PROTOCOL;
}
}
XyzLoginProtocol
public class XyzLoginProtocol implements LoginProtocol {
public static final String LOGIN_PROTOCOL = "xyz";
protected KeycloakSession session;
protected RealmModel realm;
protected UriInfo uriInfo;
protected HttpHeaders headers;
protected EventBuilder event;
public XyzLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
this.session = session;
this.realm = realm;
this.uriInfo = uriInfo;
this.headers = headers;
this.event = event;
}
public XyzLoginProtocol() {
}
#Override
public XyzLoginProtocol setSession(KeycloakSession session) {
this.session = session;
return this;
}
#Override
public XyzLoginProtocol setRealm(RealmModel realm) {
this.realm = realm;
return this;
}
#Override
public XyzLoginProtocol setUriInfo(UriInfo uriInfo) {
this.uriInfo = uriInfo;
return this;
}
#Override
public XyzLoginProtocol setHttpHeaders(HttpHeaders headers) {
this.headers = headers;
return this;
}
#Override
public XyzLoginProtocol setEventBuilder(EventBuilder event) {
this.event = event;
return this;
}
#Override
public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
log.debugf("Authenticated.. User: %s, Session Id: %s", userSession.getUser().getUsername(), userSession.getId());
try {
....
} catch (Exception ex) {
// TODO handle TokenNotFoundException exception
log.error(ex.getMessage(), ex);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
#Override
public Response sendError(AuthenticationSessionModel authSession, Error error) {
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
String redirect = authSession.getRedirectUri();
try {
URI uri = new URI(redirect);
return Response.status(302).location(uri).build();
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
return Response.noContent().build();
}
}
#Override
public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
new ResourceAdminManager(session).logoutClientSession(realm, client, clientSession);
}
#Override
public Response frontchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
throw new RuntimeException("NOT IMPLEMENTED");
}
#Override
public Response finishLogout(UserSessionModel userSession) {
return Response.noContent().build();
}
#Override
public boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession) {
return false;
}
#Override
public boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
String token = session.tokens().encode(adminAction);
log.tracev("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try {
int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
boolean success = status == 204 || status == 200;
log.tracef("pushRevocation success for %s: %s", managementUrl, success);
return success;
} catch (IOException e) {
ServicesLogger.LOGGER.failedToSendRevocation(e);
return false;
}
}
#Override
public void close() {
}
}
XyzLoginProtocolService
public class XyzLoginProtocolService {
private final RealmModel realm;
private final EventBuilder event;
#Context
private KeycloakSession session;
#Context
private HttpHeaders headers;
#Context
private HttpRequest request;
#Context
private ClientConnection clientConnection;
public XyzLoginProtocolService(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.event = event;
this.event.realm(realm);
}
#POST
#Path("request")
#Produces(MediaType.APPLICATION_JSON)
#NoCache
public Response request(ApipmLoginRequest loginRequest) {
....
}

How to registre user in my own database through keycloak

I have been using keycloak for a very short time. made keycloak use my own user database and it works fine.
I would like new users who register to be registered directly in my database. So I implemented the UseRegistrationProvider class which contains the addUser(RealmModel realm, String username) and removeUser(RealmModel realm, UserModel user) methods.
The problem is that in the addUser method I only have the username and I would like to have all the fields that have been filled on the registration form. How do I do?
Thanks
the addUser(...) method return an object implementing the UserModel. You will have to implement a UserModel adapter that enable to set the attributes you want.
See Quickstart example
Regards,
Here is my code. What did i miss?
public class MyUserStorageProvider implements UserStorageProvider,
UserRegistrationProvider,
UserLookupProvider,
UserQueryProvider,
CredentialInputUpdater,
CredentialInputValidator {
private final KeycloakSession session;
private final ComponentModel model;
private final UserRepository repository;
public MyUserStorageProvider(KeycloakSession session, ComponentModel model, UserRepository repository) {
this.session = session;
this.model = model;
this.repository = repository;
}
...
#Override
public UserModel addUser(RealmModel realm, String username) {
User user = new User();
user.setUsername(username);
user.setEmail("I don't have email in addUser method");
if(repository.addUser(user))
return new UserAdapter(session, realm, model, user);
else return null;
}
}
public class UserAdapter extends AbstractUserAdapterFederatedStorage {
private final User user;
private final String keycloakId;
public UserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, User user) {
super(session, realm, model);
this.user = user;
this.keycloakId = StorageId.keycloakId(model, user.getId());
}
#Override
public String getId() {
return keycloakId;
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public void setUsername(String username) {
user.setUsername(username);
}
#Override
public String getEmail() {
return user.getEmail();
}
#Override
public void setEmail(String email) {
user.setEmail(email);
}
#Override
public String getFirstName() {
return user.getFirstName();
}
#Override
public void setFirstName(String firstName) {
user.setFirstName(firstName);
}
#Override
public String getLastName() {
return user.getLastName();
}
#Override
public void setLastName(String lastName) {
user.setLastName(lastName);
}
}
class UserRepository {
private EntityManager em;
public UserRepository(MultivaluedHashMap<String, String> config) {
em = new JpaEntityManagerFactory(new Class[]{User.class}, config).getEntityManager();
}
...
boolean addUser(User user) {
try {
em.getTransaction().begin();
em.persist(user);
em.getTransaction().commit();
return true;
}catch(Exception e) {
return false;
}
}
}

Gwtp Autobean and rest

I have InfraNameModel (Rest-type) to work with JSON
public interface IInfraNameBeanFactory extends AutoBeanFactory {
IInfraNameBeanFactory INSTANCE = GWT.create(IInfraNameBeanFactory.class);
AutoBean<InfraNameModel> infraName();
AutoBean<InfraNameListModel> results();
}
public interface InfraNameListModel {
List<InfraNameModel> getResults();
void setResults(List<InfraNameModel> results);
}
public class InfraNameListModelImpl implements InfraNameListModel {
private List<InfraNameModel> results;
#Override
public List<InfraNameModel> getResults() {
return results;
}
#Override
public void setResults(List<InfraNameModel> results) {
this.results = results;
}
}
public interface InfraNameModel {
String getInfraName();
void setInfraName(String infraName);
}
public class InfraNameModelImpl implements InfraNameModel {
private String infraName;
#Override
public String getInfraName() {
return infraName;
}
#Override
public void setInfraName(String infraName) {
this.infraName = infraName;
}
}
I wanted to make them into a separate JAR
To make it common for the client and the server
But now I have errors
[WARN] Class by.models.infraNameModel.InfraNameModel is used in Gin, but not available in GWT client code.
Is it real to pull such beans into a separate library?

GWTP displays default place on application start even if url say to go to other place

I wonder how to change gwtp behaviour.
When I start gwt app (enter app url in browser) it always displays for me default place. But when I enter url as follow: localhost/app#settings gwtp should open place Settings, but unfortunatelly it displays me Default place.
Url in web browser address points to Settings but the view is from default place.
I would like to gwtp display for me the view from url.
Here is my configuration:
public class UiModule extends AbstractGinModule {
#Override
protected void configure() {
bind(AppView.Binder.class).in(Singleton.class);
bind(Footer.Binder.class).in(Singleton.class);
bind(GatekeeperProtectedMenuPanel.Binder.class).in(Singleton.class);
install(new GinFactoryModuleBuilder().build(MenuEntryFactory.class));
}
public class ClientModule extends AbstractPresenterModule {
#Override
protected void configure() {
bind(RestyGwtConfig.class).asEagerSingleton();
install(new DefaultModule.Builder()//
.defaultPlace(Routing.HOME.url)//
.errorPlace(Routing.ERROR.url)//
.unauthorizedPlace(Routing.LOGIN.url)//
.tokenFormatter(RouteTokenFormatter.class).build());
install(new AppModule());
install(new GinFactoryModuleBuilder().build(AssistedInjectionFactory.class));
bind(CurrentUser.class).in(Singleton.class);
bind(IsAdminGatekeeper.class).in(Singleton.class);
bind(UserLoginGatekeeper.class).in(Singleton.class);
// Load and inject CSS resources
bind(ResourceLoader.class).asEagerSingleton();
}
}
public class AppModule extends AbstractPresenterModule {
#Override
protected void configure() {
install(new UiModule());
// Application Presenters
bindPresenter(AppPresenter.class, AppPresenter.MyView.class, AppView.class, AppPresenter.MyProxy.class);
bindPresenter(HomePresenter.class, HomePresenter.MyView.class, HomeView.class, HomePresenter.MyProxy.class);
bindPresenter(ErrorPresenter.class, ErrorPresenter.MyView.class, ErrorView.class, ErrorPresenter.MyProxy.class);
bindPresenter(TestPresenter.class, TestPresenter.MyView.class, TestView.class, TestPresenter.MyProxy.class);
bindPresenter(PagePresenter.class, PagePresenter.MyView.class, PageView.class, PagePresenter.MyProxy.class);
bindPresenter(SettingsPresenter.class, SettingsPresenter.MyView.class, SettingsView.class, SettingsPresenter.MyProxy.class);
bindPresenter(FilesPresenter.class, FilesPresenter.MyView.class, FilesView.class, FilesPresenter.MyProxy.class);
bindPresenter(AdminAreaPresenter.class, AdminAreaPresenter.MyView.class, AdminAreaView.class, AdminAreaPresenter.MyProxy.class);
bindPresenter(LoginPresenter.class, LoginPresenter.MyView.class, LoginView.class, LoginPresenter.MyProxy.class);
}
}
This happens when I have GateKeeper on place's presenter.
Here is code:
public class UserLoginGatekeeper extends UserLoginModel implements Gatekeeper {
private final CurrentUser currentUser;
#Inject
UserLoginGatekeeper(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean canReveal() {
return currentUser.isLoggedIn();
}
}
In my main app presenter I execute asynhronous call to server to check is user login. If so I set client variable currentUser.setLoggedIn(true);. Base on this Gatekeeper allow access to restricted part of app.
I think the problem is that my asynhronous call is triggered to late. And GWTP redirect to default place.
Here is my app presenter code:
public class AppPresenter extends TabContainerPresenter<AppPresenter.MyView, AppPresenter.MyProxy> implements AppUiHandlers, CurrentUserChangedHandler, AsyncCallStartHandler, AsyncCallFailHandler,
AsyncCallSucceedHandler {
#ProxyStandard
public interface MyProxy extends Proxy<AppPresenter> {
}
public interface MyView extends TabView, HasUiHandlers<AppUiHandlers> {
void refreshTabs();
void setTopMessage(String string);
void setLoginButtonVisbility(boolean isVisible);
}
#RequestTabs
public static final Type<RequestTabsHandler> SLOT_REQUEST_TABS = new Type<>();
#ChangeTab
public static final Type<ChangeTabHandler> SLOT_CHANGE_TAB = new Type<>();
public static final NestedSlot SLOT_TAB_CONTENT = new NestedSlot();
private static final LoginService service = GWT.create(LoginService.class);
private final PlaceManager placeManager;
private final CurrentUser currentUser;
#Inject
AppPresenter(EventBus eventBus, MyView view, MyProxy proxy, PlaceManager placeManager, CurrentUser currentUser) {
super(eventBus, view, proxy, SLOT_TAB_CONTENT, SLOT_REQUEST_TABS, SLOT_CHANGE_TAB, RevealType.Root);
this.placeManager = placeManager;
this.currentUser = currentUser;
getView().setUiHandlers(this);
onStart();
}
protected void onStart() {
service.isCurrentUserLoggedIn(new MethodCallback<Boolean>() {
#Override
public void onFailure(Method method, Throwable exception) {
MaterialToast.fireToast("Fail to check is current user logged in " + method + " " + exception.getLocalizedMessage());
}
#Override
public void onSuccess(Method method, Boolean response) {
currentUser.setLoggedIn(response);
getView().setLoginButtonVisbility(response);
}
});
};
#ProxyEvent
#Override
public void onCurrentUserChanged(CurrentUserChangedEvent event) {
getView().refreshTabs();
}
#ProxyEvent
#Override
public void onAsyncCallStart(AsyncCallStartEvent event) {
getView().setTopMessage("Loading...");
}
#ProxyEvent
#Override
public void onAsyncCallFail(AsyncCallFailEvent event) {
getView().setTopMessage("Oops, something went wrong...");
}
#ProxyEvent
#Override
public void onAsyncCallSucceed(AsyncCallSucceedEvent event) {
getView().setTopMessage(null);
}
#Override
public void onLogoutButtonClick() {
service.logout(new MethodCallback<Void>() {
#Override
public void onFailure(Method method, Throwable exception) {
MaterialToast.fireToast("Fail to logout " + method + " " + exception.getLocalizedMessage());
}
#Override
public void onSuccess(Method method, Void response) {
MaterialToast.fireToast("You have been Succefully logout");
PlaceRequest request = new PlaceRequest.Builder(placeManager.getCurrentPlaceRequest()).nameToken(Routing.Url.login).build();
placeManager.revealPlace(request);
currentUser.setLoggedIn(false);
getView().setLoginButtonVisbility(false);
}
});
}
}
Working solution:
/**
*
*/
package pl.korbeldaniel.cms.client.gin;
import gwt.material.design.client.ui.MaterialToast;
import org.fusesource.restygwt.client.Method;
import org.fusesource.restygwt.client.MethodCallback;
import pl.korbeldaniel.cms.client.place.Routing;
import pl.korbeldaniel.cms.client.security.CurrentUser;
import pl.korbeldaniel.cms.client.service.LoginService;
import com.google.gwt.core.shared.GWT;
import com.google.inject.Inject;
import com.gwtplatform.mvp.client.Bootstrapper;
import com.gwtplatform.mvp.client.proxy.PlaceManager;
import com.gwtplatform.mvp.shared.proxy.PlaceRequest;
/**
* #author korbeldaniel
*
*/
public class MyBootstrapper implements Bootstrapper {
private final PlaceManager placeManager;
private final CurrentUser currentUser;
private static final LoginService service = GWT.create(LoginService.class);
#Inject
public MyBootstrapper(PlaceManager placeManager, CurrentUser currentUser) {
this.placeManager = placeManager;
this.currentUser = currentUser;
}
#Override
public void onBootstrap() {
GWT.log("OnBootstrap");
service.isCurrentUserLoggedIn(new MethodCallback<Boolean>() {
#Override
public void onFailure(Method method, Throwable exception) {
MaterialToast.fireToast("Fail to check is current user logged in " + method + " " + exception.getLocalizedMessage());
placeManager.revealErrorPlace("Fail to check is current user logged in " + method + " " + exception.getLocalizedMessage());
}
#Override
public void onSuccess(Method method, Boolean response) {
// MaterialToast.fireToast("1Current user is logged in: " +
// response);
currentUser.setLoggedIn(response);
if (response == true) {
placeManager.revealCurrentPlace();
} else {
placeManager.revealPlace(new PlaceRequest.Builder().nameToken(Routing.Url.login).build());
}
}
});
};
}
Yeah, your backend call is asynchronous and most likely the UserLoginGatekeeper code will run before the backend call returns and the user gets redirected to the default page.
There are two solutions:
Use a dynamically generated host page (index.html) and set a javascript variable to the user details by the backend. You can read out the userdetails in a custom Bootstraper implementation and set the CurrentUser.
If you don't want to use a dynamically generated host page, you can also move the backend call isCurrentUserLoggedIn intot he custom Bootstrapper implementation and in the onSuccess callback reveal the first page (like in the above linked GWTP documentation)

Why canReveal() of GateKeeper was called before the EventHandler was called (GWT)?

I got a Header presenter which is the nested presenter. The Customer presenter is the child of Header presenter (ie the Customer presenter was put into a slot of Header presenter).
So I want to use MyGateKeeper to manage login page. The HeaderPresenter that will fire PassUserInfoEvent.
public class MyGateKeeper implements Gatekeeper{
private String loginedUserID="";
private final EventBus eventBus;
#Inject
public MyGateKeeper (final EventBus eventBus){
this.eventBus = eventBus;
this.eventBus.addHandler(PassUserInfoEvent.getType(), new PassUserInfoHandler(){
#Override
public void onPassUserInfo(PassUserInfoEvent event) {
// TODO Auto-generated method stub
String userID=event.getUserID();
loginedUserID=userID;
}
});
}
#Override
public boolean canReveal(){
System.out.println(loginedUserID+"Test");
if(!loginedUserID.equals("")){
System.out.println(loginedUserMeaningID+"cxcxc");
return true;
}
else{
return false;
}
}
}
In the CustomerPresenter:
#ProxyCodeSplit
#NameToken(NameTokens.cust)
#UseGatekeeper(MyGateKeeper.class)
public interface MyProxy extends ProxyPlace<CustomerPresenter> {
}
However after run, it does not show the Gui even I loggined. I tested & found that canReveal() in MyGateKeeper was called before PassUserInfoHandler() was called, so canReveal never return true;
How to fix this problem?
The usual pattern to accomplish this is to bind a CurrentUser class in Singleton:
bind(CurrentUser.class).in(Singleton.class);
and inject it into your GateKeeper. Inside your GateKeeper's canReveal method, you'll check that currentUser.isLoggedIn() :
private final CurrentUser currentUser;
#Inject
MyGateKeeper(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean canReveal() {
return currentUser.isLoggedIn();
}
You should initialize the CurrentUser.isLoggedIn field inside your Bootstrapper (see https://github.com/ArcBees/GWTP/wiki/Bootstrapping-or-Application-Initialization) by calling your server. Here's an example using GWTP's RestDispatch:
public class BootstrapperImpl implements Bootstrapper {
private final String unauthorizedPlace;
private final CurrentUser currentUser;
private final PlaceManager placeManager;
private final RestDispatch restDispatch;
private final UserResource userResource;
#Inject
BootstrapperImpl(
#UnauthorizedPlace String unauthorizedPlace,
CurrentUser currentUser,
PlaceManager placeManager,
RestDispatch restDispatch,
UserResource userResource) {
this.unauthorizedPlace = unauthorizedPlace;
this.currentUser = currentUser;
this.placeManager = placeManager;
this.restDispatch = restDispatch;
this.userResource = userResource;
}
#Override
public void onBootstrap() {
checkIfUserIsLoggedIn();
}
private void checkIfUserIsLoggedIn() {
restDispatch.execute(userResource.isCurrentUserLoggedIn(), new AbstractAsyncCallback<Boolean>() {
#Override
public void onSuccess(Boolean isCurrentUserLoggedIn) {
navigate(isCurrentUserLoggedIn);
}
});
}
private void navigate(Boolean isCurrentUserLoggedIn) {
currentUser.setLoggedIn(isCurrentUserLoggedIn);
if (isCurrentUserLoggedIn) {
placeManager.revealCurrentPlace();
} else {
placeManager.revealPlace(new PlaceRequest.Builder().nameToken(unauthorizedPlace).build());
}
}
}