Giter Club home page Giter Club logo

keycloak-api-key-demo's Introduction

Keycloak extension for API key authentication

The extension contains providers for supporting API key authentication, and also other non related providers like a custom EmailSenderProvider (for demo purposes).

It also contains a customization of the account console (the user info page provided by Keycloak) showing the API key. The account console is accessible at /auth/realms/{realm_name}/account and requires the user to be already authenticated.

The master branch uses the new Keycloak distribution powered by Quarkus. For Legacy keycloak (versions < 17.0.0), you can switch to the legacy branch.

How to run

you can run the project by running the following from a terminal: mvn -f api-key-module package && mvn -f dashboard-service package && docker-compose up

Note: You need to add auth-server to your hosts file (/etc/hosts for linux) and map it to localhost.

Testing

  1. Navigate to localhost:8180 in a browser, you will redirected to keycloak for authentication
  2. you need register a new user, after which you will be redirected to the main dashboard page which will show your API key
  3. copy the API key and use it to call the API: curl -v -H "x-api-key: $THE_API_KEY" localhost:8280, if you omit the API key, you will get 401 status

More explanations can be found in this blog post

keycloak-api-key-demo's People

Contributors

zak905 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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

keycloak-api-key-demo's Issues

Error MediaType not set on path /auth/realms/example/check

Hi and thank you for this guide.

I have an error when I try to access the API with the correct API key, do you know why I get this?

My env : Docker on macOS Big Sur

Edit: Same problem on Ubuntu

Thank you!

rest-api-service_1   | checking api key vly8NJ5xRLV5eHcsRn38J0oUBuWYA6kw7tVAvwhQ9MHI2DgBKX, auth server auth-server:8080
auth-server_1        | 14:18:58,009 ERROR [org.keycloak.headers.DefaultSecurityHeadersProvider] (default task-5) MediaType not set on path /auth/realms/example/check, with response status 200
auth-server_1        | 14:18:58,010 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-5) Uncaught server error: javax.ws.rs.InternalServerErrorException: HTTP 500 Internal Server Error
auth-server_1        | 	at [email protected]//org.keycloak.headers.DefaultSecurityHeadersProvider.addHeaders(DefaultSecurityHeadersProvider.java:75)
auth-server_1        | 	at [email protected]//org.keycloak.services.filters.KeycloakSecurityHeadersFilter.filter(KeycloakSecurityHeadersFilter.java:39)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.interception.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:357)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:219)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:95)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:69)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:530)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:461)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:245)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:61)
auth-server_1        | 	at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
auth-server_1        | 	at [email protected]//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
auth-server_1        | 	at [email protected]//org.keycloak.provider.wildfly.WildFlyRequestFilter.lambda$doFilter$0(WildFlyRequestFilter.java:41)
auth-server_1        | 	at [email protected]//org.keycloak.services.filters.AbstractRequestFilter.filter(AbstractRequestFilter.java:43)
auth-server_1        | 	at [email protected]//org.keycloak.provider.wildfly.WildFlyRequestFilter.doFilter(WildFlyRequestFilter.java:39)
auth-server_1        | 	at [email protected]//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
auth-server_1        | 	at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
auth-server_1        | 	at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
auth-server_1        | 	at [email protected]//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
auth-server_1        | 	at [email protected]//io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
auth-server_1        | 	at [email protected]//io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
auth-server_1        | 	at [email protected]//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
auth-server_1        | 	at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
auth-server_1        | 	at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
auth-server_1        | 	at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
auth-server_1        | 	at [email protected]//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
auth-server_1        | 	at [email protected]//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
auth-server_1        | 	at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
auth-server_1        | 	at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
auth-server_1        | 	at [email protected]//io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
auth-server_1        | 	at [email protected]//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:841)
auth-server_1        | 	at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
auth-server_1        | 	at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
auth-server_1        | 	at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
auth-server_1        | 	at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
auth-server_1        | 	at [email protected]//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
auth-server_1        | 	at java.base/java.lang.Thread.run(Unknown Source)

API key generation security

Digging the plugin.

I was wondering whether it would be possible, difficult for me to answer due to lack of full Keycloak knowledge, to have the option of having a strategy to influence the way the API key is generated. Currently it is a UUID, but perhaps a strategy of generating a random secure string would also be nice to set the size of the key.

Similarly, I see that currently the API key would be stored plain text, would it be possible to store this encrypted with the option to obtain this API key again?

Perhaps above strategy could also resolve the comment on the blog post:

Just so you know, this implementation is vulnerable to timing attacks. The best way I have seen to handle this is to split the API key into two pieces: a prefix and a suffix. Index and select using the prefix (sacrificing that entropy to timing attacks) and treat the suffix as a password, complete with hashing, etc.

Reason being is that a UUID is time based and server oriented, meaning it could be derived given that information.

With Keycloak 19.0.2 I can't get the API Key after user registration

With Keycloak 19.0.2, after launching the whole thing and after the user registration it give me the errors:

keycloak-api-key-demo-auth-server-1        | 2022-09-29 17:50:10,469 WARN  [org.keycloak.events] (executor-thread-5) type=CODE_TO_TOKEN_ERROR, realmId=example, clientId=dashboard-client, userId=null, ipAddress=172.20.0.2, error=invalid_client_credentials, grant_type=authorization_code
keycloak-api-key-demo-dashboard-service-1  | 2022-09-29 17:50:10.470 ERROR 1 --- [nio-8180-exec-1] o.k.adapters.OAuthRequestAuthenticator   : failed to turn code into token
keycloak-api-key-demo-dashboard-service-1  | 2022-09-29 17:50:10.470 ERROR 1 --- [nio-8180-exec-1] o.k.adapters.OAuthRequestAuthenticator   : status from server: 401
keycloak-api-key-demo-dashboard-service-1  | 2022-09-29 17:50:10.470 ERROR 1 --- [nio-8180-exec-1] o.k.adapters.OAuthRequestAuthenticator   :    {"error":"unauthorized_client","error_description":"Invalid client secret"}

With Keycloak 19.0.1 it works like a charm.

Event listener 'api-key-registration-generation' registered, but provider not found

Hey Zakaria,

I followed your blog post and it looks like what I'm looking for. After trying your example I get this error. Any idea what's going on here?

Cheers!

auth-server_1        | 03:12:22,081 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
auth-server_1        | 03:12:22,081 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
dashboard-service_1  | 2021-07-02 03:12:41.090  INFO 1 --- [nio-8180-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
dashboard-service_1  | 2021-07-02 03:12:41.091  INFO 1 --- [nio-8180-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
dashboard-service_1  | 2021-07-02 03:12:41.106  INFO 1 --- [nio-8180-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 15 ms
dashboard-service_1  | 2021-07-02 03:12:41.627  INFO 1 --- [nio-8180-exec-2] o.keycloak.adapters.KeycloakDeployment   : Loaded URLs from http://auth-server:8080/auth/realms/example/.well-known/openid-configuration
auth-server_1        | 03:12:41,643 ERROR [org.keycloak.events.EventBuilder] (default task-1) Event listener 'api-key-registration-generation' registered, but provider not found
auth-server_1        | 03:12:44,921 ERROR [org.keycloak.events.EventBuilder] (default task-4) Event listener 'api-key-registration-generation' registered, but provider not found
auth-server_1        | 03:12:58,087 ERROR [org.keycloak.events.EventBuilder] (default task-4) Event listener 'api-key-registration-generation' registered, but provider not found
auth-server_1        | 03:12:58,126 ERROR [org.keycloak.events.EventBuilder] (default task-4) Event listener 'api-key-registration-generation' registered, but provider not found
auth-server_1        | 03:12:58,442 ERROR [org.keycloak.events.EventBuilder] (default task-4) Event listener 'api-key-registration-generation' registered, but provider not found
auth-server_1        | 03:12:58,577 ERROR [org.keycloak.events.EventBuilder] (default task-4) Event listener 'api-key-registration-generation' registered, but provider not found```

Display attribute to user within keycloak

If a user visits their account area, they're able to view details about their account:

<domain>/auth/realms/<realm>/account/

image

Is it possible to include the API attribute in here? If so, what steps should I take?

Ideally I'd like to use this instead of a separate service for displaying their API key upon a successful authentication

Example regarding backend use

Hi Zakaria, thanks for this great example.
I've read your blog post about the possibility to add API-Key functionality to keycloak.
In your example the backend has to check the key.
Is there any way to return a signed JWT from keycloak if the api key is valid?
This would enable the usage of the same backend logic for oidc users and api users.

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.