I'm trying to implement a custom keycloack Authenticator SPI for authentication purposes against an external Datasource/Rest Service. The plan is to migrate them into keycloak.
Updated the browser flow(copy and created a new flow and bind it).
First time when the user logs in, will display custom theme login screen.
On submit Using Custom SPI authenticate against external service.
On success create users on the keycloak datasource.
Create custom mappers to add extra user attributes on tokens.
I'm following section 8.3 of the official guide https://www.keycloak.org/docs/latest/server_development/index.html#_auth_spi_walkthrough, which is very similar to what I need.
Also, followed examples(https://github.com/keycloak/keycloak/tree/master/examples/providers/authenticator) I think the instructions doesn't map it to the new version.
For example:
In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question.
In Keycloak-6.0.1, there isn't any such execution as "Pick Secret Question". I really don't need this, but I was atleast trying to set this flow to get hold on how the flow works.
Next you have to register the required action that you created. Click on the Required Actions tab in the Authenticaiton menu. Click on the Register button and choose your new Required Action.
There isn't such Register button on the required action.
Things I have done.
Created new realm
Registered UI client
Created new user in new realm in keycloak tables.
UI on login redirects to Keycloak UI and successfully authenticated and able to retrieve successfully tokens using javascript adapters
componentDidMount = () => {
const keycloak = Keycloak('/keycloak.json');
keycloak.init({onLoad: 'login-required'}).then(authenticated => {
this.setState({ keycloak: keycloak, authenticated: authenticated })
})
}
....
....
if(this.state.keycloak) {
if(this.state.authenticated) return (
<div className="contact-body">
<p>Name: {this.state.name}</p>
<p>Email: {this.state.email}</p>
<p>ID: {this.state.id}</p>
</div>
);
}
}
Would like to use Keycloak Login UI features(like OTP) along with custom authenticator SPI. In the custom authenticator SPI get form fields like username and password and authenticate it using external service. Then create users in Keycloak DB.
Can admin lock/temporarily suspend user within a realm in keycloak?
The other option, I am planning to use is, update login.ftl onsubmit to post username/password to custom service with keycloak url passed as query param.
Will validate it against custom service, create users on keycloak database and redirect to keycloak url which is passed on query param. This doesn't seem like right way.
Any help/thoughts will be highly helpful.
This is the wrong SPI you are implementing.
For external datasource or service integration you must implement a custom User Storage SPI.
You should use "User Storage SPI". It would be the Section 11 on Keycloak Doc
Section 8, is needed if you need extra/custom authentication like secret questions.
Related
I'd like to add a new auth method in keycloak. To be precise - I'd like the keycloak to ask external API for some specific value. I have read about flows in keycloak but they seem to be poorly documented and I have a feeling that it is not very intuitive.
During login I would like the keycloak to send request to external API and if and only if when specific value is returned allow the user to login. For example I could override some login method and add a few lines of code doing what I want.
Which method in which class is responsible for login?
There are multiple things you need to do to achieve that. I will go over them:
Implement Authenticator and AuthenticatorFactory interfaces.
Copy an existing Authentication Flow
Bind flow
I assume you know how to write and deploy a keycloak extension.
1. Implement Authenticator and AuthenticatorFactory interfaces.
The specific interfaces are those:
org.keycloak.authentication.AuthenticatorFactory
org.keycloak.authentication.Authenticator
A sample implementation:
org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory
org.keycloak.authentication.authenticators.browser.UsernamePasswordForm
If you want to externalize your config (So you can add username/password etc. for external api), override getConfigProperties() method in AuthenticatorFactory
2. Copy an existing Authentication Flow.
Login keycloak with admin credentials.
Create a new realm (or use if you have one)
Go to Authentication tab on left.
Copy browser login flow
Add your flows/executions (Your implementation of Authenticator/Factory will be listed under executions)
You can move them up or down. Make them required or alternative etc.
If you override config list it will be shown next to your execution
3. Bind flow.
Bind your flow in the second tab of Authentication page.
I'm trying to implement a custom keycloack Authenticator SPI for authenticating against an external Datasource. Spring boot Rest Service is also available, I can also use that.
Use case I am trying to solve is
User is presented keycloak login screen. Onsubmission User is validated against external Datasource.
Retrieve some attributes from external datasource, map it to keycloak's id and access token.
Also put in a condition of user restriction of same user logging in multiple times at the same time.
I was thinking, it could be solved by retrieving user session information that's available in the keycloak datasource. If i use external datasource, does keycloak still maintain session information?
I followed section 8.3 of the official guide (https://www.keycloak.org/docs/latest/server_development/index.html#_auth_spi_walkthrough), which is very similar to what I need.
Now i skipped and started as per section 11(https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi) seems more apt as well.
What I have done is started with implementing custom authenticator SPI thought it isn't the right way and now implemented UserStorageProvider.
/***
* From UserLookupProvider
*/
public UserModel getUserById(String id, RealmModel realm) {
System.out.println("ID: " + id + ":REALM:" + realm);
StorageId storageId = new StorageId(id);
/**
* StorageId.getExternalId() method is invoked to obtain
* the username embeded in the id parameter
*/
String username = storageId.getExternalId();
System.out.println("Name:" + username);
return getUserByUsername(username, realm);
}
/***
* From UserLookupProvider
* This method is invoked by the Keycloak login page when a user logs in
*/
public UserModel getUserByUsername(String username, RealmModel realm) {
System.out.println("USERNAME: " + username + ":REALM:" + realm);
UserModel userModel = loadedUsers.get(username);
if (userModel == null) {
String password = properties.getProperty(username);
if (password != null) {
userModel = createUserModel(realm, username);
System.out.println("New UserModel:");
System.out.println(userModel.toString());
loadedUsers.put(username, userModel);
}
}
return userModel;
}
protected UserModel createUserModel(RealmModel realm, String username) {
return new AbstractUserAdapter(session, realm, model) {
#Override
public String getUsername() {
return username;
}
};
}
Followed the doc(https://www.keycloak.org/docs/latest/server_development/index.html#packaging-and-deployment-2)
The class files for our provider implementation should be placed in a jar. You also have to declare the provider factory class within the META-INF/services/org.keycloak.storage.UserStorageProviderFactory file.
Question here is: jar I created doesn't have services directory inside "META-INF" folder, do I need to create manually and add it?
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
Once you create the jar you can deploy it using regular WildFly means: copy the jar into the deploy/ directory or using the JBoss CLI.
After creating jar using maven, copied jar to "keycloak-6.0.1\standalone\deployments" folder. but i don't see my provider in the "User Federation list"
Any suggestion/help would be greatly appreciated!!
Thanks in advance for your suggestions.
Incase if anybody ran into issues like this:
UserStorage SPI wasn't displaying because of META-INF/services folder. It's provided in the documentation but it isn't clear
In src/main/resources, create a folder structure META-INF/services
Create a file called org.keycloak.storage.UserStorageProviderFactory (the whole thing is the filename) in META-INF/services directory. Its contents is the fully qualified class name of your SPI:
com.test.UserSpi
Ok you clarified you need a User Store Provider API. Great
Now as for your 2nd "Problem/Challenge":
Retrieve some attributes from external datasource, map it to keycloak's id and access token. Need to retrieve users unique id and add it as subject id in the jwt. That's the id, rest of the services can use to retrieve the id, when this token is passed to other services.
For this, the best you can do is:
Add those user's unique data as users attributes (see them on the Admin console)
Create a "Client scope" on Keycloak, with a mapper for "user property"
to map those properties you'd like to add (from your user) to your Id-token and access-token. You also need to tie your client with your just created "client scope". This may sound a little bit confusing, but this video is great material and I bilieve it'll help you alot: https://www.youtube.com/watch?v=ZxpY_zZ52kU (arround min 6:30 you'll see how to add extra user info to your tokens)
also check this page out: https://jwt.io/ (when you paste there encoded tokens, you can see their contents), it is a great tool for developers.
When you advance in your solution I'll help with the unique session, or you post that as a different question, as that's a different problem.
Hope it helps.
I'm not exactly sure what do you need. Lets start by differentiating Authentication SPI (federated identity check) vs User Provider SPI (federated users). The first one (section 8 of the doc - is more about focusing on authenticating users against an external service - similar to facebook, or google). Federated user store is more like you have your own users in a legacy system with their legacy "roles structure", and you basically want to manage them via keycloak (either by importing them, or by querying the via some API - this would be section 11 of that documentation). So please decide what is indeed what you need.
2nd, you mention following:
> User is presented keycloak login screen. Onsubmission User is
> validated against external Datasource.
>
> Retrieve some attributes from external datasource, map it to
> keycloak's id and access token.
>
> Also put in a condition of user restriction of same user logging in
> multiple times at the same time.
>
> I was thinking, it could be solved by retrieving user session
> information that's available in the keycloak datasource. If i use
> external datasource, does keycloak still maintain session information?
What do you mean by: Retrieve some attributes from external datasource, map it to keycloak's id and access token. ? Usually you only retrieve user core information, plus maybe roles and other custom attributes (not session information). Keycloak itself as an Authorization Server based on openIDConnect, will generate acces tokens which already contains information about what protected resource can by accessed by whom, so you dont really need to import any session from somewhere else, or concern yourself with the generation of said tokens.
regarding: Also put in a condition of user restriction of same user logging in
multiple times at the same time. what exactly are you trying to accomplish (or avoid?) when you log the 1st time your client receives a Bearer token valid for X amount of time, within that time you wont need to log yourself again, until the token expires or is roveked; again something your Auth server takes care of, not something you implement. Is there something more specific you want?
I was thinking, it could be solved by retrieving user session information that's available in the keycloak datasource. If i use external datasource, does keycloak still maintain session information? This doesn't sound right, what session data do you refer to? or you need access to? your userdata, scopes, roles, etc can be accessed via KEycloak Rest API (https://www.keycloak.org/docs-api/6.0/rest-api/index.html#_overview). Your external datasource is for user related core data (not external sessions), why do you think you need to import an external session?
I have a REST service implemented using Spring MVC (RestControllers) with token based security (using Spring Security). How can i filter resources depending on user identity? Let's say user has some reports. How can I let authorized user by performing a call to /reports to see only his reports?
Obviously i can make userId to be a request parameter or path variable, but something tells me that this is a bad practice.
I assume i can achieve that using Spring Security features, but how exactly could i do that and, more important, where is the most appropriate place to apply such filtering? Should controllers perform calls to services passing user identity or should it be somehow retrieved at repositories level (I use Spring Data JPA)?
Thanks in advance
You have Authentication object whenever a user is successfully logged in.
It contains Object principal Object credentials and Set authorities.
All you need to do is override UserDetailsService to add new parameters for your authenticated user. Add your userId in authentication as shown in blog
Now when you do
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
this will return you the User object of the spring security.
You can get the user id from here and use this in controller to do necessary actions.
first post here, hope im doing right.
In a project, we have a scenario where we have a single web application with multiple entities. Currently, the login is managed via default JDBC Spring Security provider, working fine.
For a new requirement, we need that each entity can have their own login method (currently 2 methods would be available, the JDBC one, which is the current one, and the second method would be authentication via SAML, with each entity defining their own IdP, but this is another story)
I need some guidelines on how this can be achieved, I have done some search and I have found providers for different URL's, etc... But not different login methods for the same app and url's depending on the user type or entity.
Is a good approach to have a custom single entry point where we can check the entity user and then use the suitable authentication provider?
Kind regards,
Alex
As each of your users might be using a different IDP you will in any case need to determine the username before proceeding with initialization of the authentication process - but you already know this.
One approach to take (similar to what Microsoft is using with the Office 365 for corporate users) is:
display a login page with fields for standard username + password
once user enters username and blurs the input field, you make an AJAX call (to your custom API made for this purpose) and fetch information about authentication type + IDP to use for this user
in case the type is password you simply let user continue with filling in the password field and POST to the same place as you're used to for processing with the JDBC provider
in case the type is federated authentication you initialize authentication with the correct IDP by redirecting to /saml/login?idp=xyz and continue with the SAML flow
It's possible to avoid any APIs by submitting the form once user enters the username, or let user click a "Continue" button. It would then make sense to use a custom EntryPoint which:
redirects user to the main login page in case it wasn't provided with a username
displays either login page with username/password or redirects to the correct IDP, once username was provided
I'm working on a cloud endpoints backend and want to restrict certain operations to admin users.
My current code works like this:
#ApiMethod(httpMethod = "PATCH", name = "item.update", path = "items")
public Item update(Item newObject, User user)
throws UnauthorizedException, OAuthRequestException {
OAuthService oAuthService = OAuthServiceFactory.getOAuthService();
if (!oAuthService.isUserAdmin()) {
throw new UnauthorizedException("Only admin users can modify content.");
}
...
}
I know app engine has a concept of user roles, but I'm curious if Endpoints
do. I've tried using the OAuthService.isUserAdmin() call but that doesn't
seem to be working out very well and the docs have a big old warning saying
Note: You should not confuse Endpoints auth with the auth for
non-Endpoints App Engine web apps described in the article on configuration settings
https://developers.google.com/appengine/articles/auth in the Admin
Console, where you also specify the user login requirement in your
web.xmlhttps://developers.google.com/appengine/docs/java/config/webxml#Security_and_Authentication
file. That approach is not used with Endpoints."
Do I have to create some sort of authorization myself that uses the User object that's passed into the update method? Any thoughts?
I had similar issues. Indeed OAuth user service has nothing to do with AppEngine user service. What I ended up doing was having a dedicated user type entity in my datastore where I store a specific flag (regular/admin) for each user. This flag is updated when I use AppEngine user service (i.e. so that the administrators I specified in the console get the proper admin flag).
In my endpoints API I get the current user authDomain and id, look up in my datastore to check whether it has the admin flag. The key of my user entity is composed of "authDomain:userId" and as I only support google user for now, it looks like (gmail.com:123456789)
This means that an administrator has to login once using the AppEngine UserService (i.e. a dedicated webpage in my case) so that the flag is properly updated
I needed to do the same thing and validate some endpoint to grant access only to admin members listed in the project console and used the same implementation presented above, but the oAuthService.isUserAdmin() accept one or more string parameters, this parameters are scopes that you specify and the Oauth uses to get user informations, in my case i just set this parameter and it works like the code bellow.
OAuthService authService = OAuthServiceFactory.getOAuthService();
User user;
try {
com.google.appengine.api.users.User currentUser =
authService.getCurrentUser(Constants.EMAIL_SCOPE);
if (currentUser != null && authService.isUserAdmin(Constants.EMAIL_SCOPE)) {
user = new User(currentUser.getEmail());
return user;
}
...
The EMAIL_SCOPE constant is defined by
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
In my case i implemented an authenticator, to pass user information to endpoint only if it's admin user, you can read more about the authenticators if you want.
https://cloud.google.com/appengine/docs/java/endpoints/javadoc/com/google/api/server/spi/config/Authenticator