I need to handle user's cancel in custom Keycloak SPI Authenticator.
I implemented it and it works fine. All I need is to cancel login flow and start from the begging when user hit cancel
#Override
public void action(AuthenticationFlowContext context) {
logger.debug("action called ... context = " + context);
String cancel = context.getHttpRequest().getDecodedFormParameters().getFirst("cancel");
if (cancel != null) {
context.cancelLogin();
context.resetFlow();
return;
}
// OK handling...
}
In my case I'm getting login page but with wrong URL:
http://localhost:8080/auth/realms/realm1/login-actions/authenticate?execution=bb1fb7c3-0b59-4a07-b997-b619c6f9ea2a&client_id=realm1-client&tab_id=YJxYk7osJaU
instead of URL like this when I enter secured page first time:
http://localhost:8080/auth/realms/realm1/protocol/openid-connect/auth?response_type=code&client_id=realm1-client&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fsso%2Flogin&state=2cc038b9-2c69-4648-ac39-e5864bc05ee9&login=true&scope=openid
Ah all I need is to left only row (or put it the last before return):
context.cancelLogin();
On my custom application I need to handle endpoint with parameters where KK will redirect me
/sso/login?error=access_denied&state=923e5b18-1d8e-4c96-9fed-c1dd122065bb
Related
I'm working on an authentication plugin that uses JWT parsing to get details and update the user in Mesh.
I'd like to also create a new node and attach it to the User in Mesh, using the user.setNodeReference() // Is this how I associate a User to a node?
The problem is when I return the mapping result, if I create the user profile node, I see the mapToken() method invoked again with the same token as before, like it's looping. I've found this is due to the 'retry' capabilities in the router
If I dont attach a node to the user.nodeReference() then it proceeds as expected.
Thoughts?
#Override
public MappingResult mapToken(HttpServerRequest req, String uuid, JsonObject token) {
MappingResult result = new MappingResult();
if (uuid == null) {
log.info("First time login of the user");
} else {
log.info("Already synced user is logging in.");
}
log.info("Mapping user in plugin");
printToken(token);
String username = extractUsername(token).get();
UserUpdateRequest user = new UserUpdateRequest();
user.setUsername(username);
user.setEmailAddress(username);
user.setFirstname(token.getString("firstname", "firstname"));
user.setLastname(token.getString("lastname", "lastname"));
// TODO: Stop the infinite loop
if (uuid == null) {
log.info("Creating profile node");
user.setNodeReference(createProfileNode(username, token));
} else {
log.info("Updating profile node");
//updateProfileNode(uuid, token);
}
result.setUser(user);
...
}
private ExpandableNode createProfileNode(String username, JsonObject token) {
NodeCreateRequest nodeCreateRequest = new NodeCreateRequest()
.setLanguage("en")
.setSchemaName(getConfig().getProfileSchema())
.setParentNodeUuid(getConfig().getProfileParentUuid());
FieldMap fields = nodeCreateRequest.getFields();
fields.putString("name", username);
fillProfileFieldMappedValues(fields, token);
nodeCreateRequest.setFields(fields);
return this.adminClient.createNode(getConfig().getProjectName(), nodeCreateRequest).blockingGet();
}
Update
I checked the jti & iat - the token contains both.
I thought maybe if I subscribe to the USER_CREATED event, I could add a profile node after the user is created.
But I don't see this ever executed. I may be incorrectly subscribing to the local event bus.
getRxVertx().eventBus().localConsumer(MeshEvent.USER_CREATED.getAddress()).handler((message) -> {
try {
String uuid = JsonUtil.getMapper().readTree(message.body().toString()).get("uuid").asText();
adminClient().findUserByUuid(uuid).toSingle().doAfterSuccess(u -> {
u.setNodeReference(createProfileNode(u.getUuid()).getBody());
}).doOnError(e -> {
log.error("Failed to create user profile node: {}", e);
});
} catch (IOException e) {
log.error("Failed to deserialize user: {}", e);
}
});
Also, I don't need to set the user.setNodeReference() to reproduce the error, I only need to try creating a new node in the mapToken method. It will retry creating the user 10x then error out with an http 500.
I'll turn up logging to see if I can get more details.
Update
I've found that if I create the user first in the mapToken function, then create a node for the profile, I can add it to the user.setNodeReference() but I never see the node in the content browser [I create it at `{project}/profiles/{userProfileNode}], and I'm not able to see the node reference when I retrieve the user.
But the logs show the node was created successfully.
Does your token contain a token Id? (jti or iat). Mesh will use one of these values to determine whether the key mapping needs to be re-run for the token. The idea behind this is to avoid bogus mapping calls for tokens that have not changed. I suspect your token does not pass this check and will be passed always to the mapper plugin.
I might be able to give you more hints if I could see some logs.
I'm making a GWT application that requires users to log in. If username and password are correct, then they are allowed to use the application.
What needs to be implemented in the onSuccess() method to make this possible ?
Thanks in advance.
DBConnectionAsync rpcService = (DBConnectionAsync) GWT.create(DBConnection.class);
ServiceDefTarget target = (ServiceDefTarget) rpcService;
String moduleRelativeURL = GWT.getModuleBaseURL() + "DBConnectionImpl";
target.setServiceEntryPoint(moduleRelativeURL);
rpcService.authenticateUser("admin", "admin", new AsyncCallback<User>() {
#Override
public void onSuccess(User result) {
// What to do here to open or redirect the user to a new page ?
}
#Override
public void onFailure(Throwable caught) {
// Failure
}
});
A simple way of doing this would be to fire an event to the eventbus of you application and then catch this event in the main controller of your application, which would trigger the opening of the right page.
This two pages should explain everything you possibly need to know to do this:
Everything about the architecture GWT MVP
Everything about activity and places (navigation and history)
Just as if you need more information.
From onSuccess you can call a method which will be defined in a corresponding presenter and from that method you can fire an event which will allow user to enter into your application only on successful authentication .
In my vaadin web app, an admin user should be able to forcefully logout a currently logged in user. When a user is forcefully logged out, he should be immediately redirected to the login page and an error message should be shown to the user that he has been forcefully logged out.
So far, I have written following code, which successfully logs out the user to the login page.
try {
vaadinSession.lock(); //The session to be forcefully logged out
try {
vaadinSession.getUIs().forEach(ui -> {
if (ui.getPage() != null) {
ui.getPage().setLocation("");
ui.push();
Notification notification = new Notification("You have been forcefully logged out", Notification.Type.WARNING_MESSAGE);
notification.setDelayMsec(-1);
notification.show(ui.getPage());
ui.push();
}
});
} catch (Exception e) {
logger.error("Exception triggered when redirecting pages on forceDisconnect " + e.getLocalizedMessage(), e);
}
vaadinSession.close();
} finally {
vaadinSession.unlock();
}
But, the notification shown in the code is not actually shown to the user. I think it is because a new Vaadin session gets created when vaadinSession.close(); is called. If I show a notification in the new vaadin session, I think it will successfully get displayed.
But, I have no idea how I can access the new session after I call vaadinSession.close();.
Can someone point me a way how I can achieve this?
May not be ideal, but following is how I got this done finally.
In forceDisconnect() method, set the message as a session variable in the underlying session of VaadinSession
vaadinSession.getSession().setAttribute("PrevSessionError", "You have been forcefully logged out");
In attach() of the login view, show the message to the user if previously set variable was found.
#Override
public void attach() {
super.attach();
Object previousSessionError = getSession().getSession().getAttribute("PrevSessionError");
if (previousSessionError != null) {
Notification notification = new Notification(previousSessionError.toString(), Notification.Type.ERROR_MESSAGE);
notification.setDelayMsec(-1);
notification.show(getUI().getPage());
getSession().getSession().setAttribute("PrevSessionError", null);
}
}
This works because the underlying session does not change even when VaadinSession gets changed. I don't know whether this is reliable or not, but this is all I could do.
I have developed a login form for my app. The app works successfully.
This is my current o/p :
The problem is If I have to enter the correct login information means its displayed success message after that go to next activity.
If i have to enter the wrong information means just displayed login failed message and redirect to same login page.
Wish to need the o/p like:
If I have to enter the correct login details means its directly go to next activity and shouldn't display success message.
But if I have to enter the wrong login details means login fail message should be displayed.
In this app.,
I have used below code in WebService:
if(retrievedUserName.equals(userName)&&retrievedPassword.equals(password)&&!(retrievedUserName.equals("") && retrievedPassword.equals(""))){
status = "Success";
also wrote the below condition on android side:
if(status.equals("Success"))
Now if I have to enter my login username and password correctly, it will display Success message and go to next activity. If login is failed, login failed message is displayed here successfully.
But same time I have to write the code on webservice side:
if(retrievedUserName.equals(userName)&&retrievedPassword.equals(password)&&!(retrievedUserName.equals("") && retrievedPassword.equals(""))){
status = "";
also wrote the android side means
if(status.equals(""))
Now when I enter my login username and password correctly, it shouldn't display success message also nothing is happened.
But If I enter the wrong login details, login failed message is displayed properly.
Assuming retrievedUserName and retrievedPassword are fetched from database, change the web service as below.
if(retrievedUserName.equals(userName)&&retrievedPassword.equals(password)){
status = "Success"; //login success
} else {
status = ""; //login failed
}
Coming to calling web service from UI, use AsyncTask class as below. Try not to use network calls in the main UI thread.
class BgRunner extends AsyncTask<String, Void, String> {
#Override
protected Object doInBackground(String... params) {
//perform login authentication
//call webservice method
return <result of authentication>
}
#Override
protected void onPostExecute(String loginStatus) {
if(null != loginStatus && loginStatus.equals("Success")) {
//start new activity
} else {
//login failed
//print login failed message on UI
}
}
}
Read AsyncTask for more details.
a small piece of advice. Keep the question simple and concise! You receive prompt responses that way.
By the way, I reckon this answer will keep you going.
I am using spring security with URL based interceptors to secure my application. In which classes/at which points can I do some custom processing after a user logged in?
I specifically want to save the date the user logged in last, but I cannot figure out how to achieve this.
Thanks a lot for your help.
You could consider implementing the org.springframework.context.ApplicationListener interface.
You would then listen specifically for the org.springframework.security.authentication.event.AuthenticationSuccessEvent.
You could then persist your user's login.
Possible example code:
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof AuthenticationSuccessEvent) {
try {
AuthenticationSuccessEvent authenticationSuccessEvent = (AuthenticationSuccessEvent) event;
Authentication authentication = authenticationSuccessEvent.getAuthentication();
//Persist your user's login here.
} catch (Exception e) {
// Handle exception as needed.
}
}
}