{"id":1862,"date":"2025-01-31T22:14:12","date_gmt":"2025-01-31T14:14:12","guid":{"rendered":"https:\/\/www.fanyamin.com\/wordpress\/?p=1862"},"modified":"2025-01-31T22:14:12","modified_gmt":"2025-01-31T14:14:12","slug":"use-oidc-and-keycloak-to-protect-your-api","status":"publish","type":"post","link":"https:\/\/www.fanyamin.com\/wordpress\/?p=1862","title":{"rendered":"Use OIDC and keycloak to protect your API"},"content":{"rendered":"<h2><strong>1. Setup Keycloak<\/strong><\/h2>\n<p>Before you start coding, make sure you have a running Keycloak instance:<\/p>\n<h3><strong>Run Keycloak using Docker<\/strong><\/h3>\n<pre><code class=\"language-sh\">docker run -d --name keycloak -p 8080:8080 \\\n    -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \\\n    quay.io\/keycloak\/keycloak:latest start-dev<\/code><\/pre>\n<h3><strong>Create a Realm and Client<\/strong><\/h3>\n<ol>\n<li>Open Keycloak at <code>http:\/\/localhost:8080\/<\/code><\/li>\n<li>Login with <code>admin\/admin<\/code><\/li>\n<li>Create a new <strong>Realm<\/strong> (e.g., <code>myrealm<\/code>)<\/li>\n<li>Under the Realm, create a <strong>Client<\/strong>:\n<ul>\n<li>Client ID: <code>myclient<\/code><\/li>\n<li>Client Type: <strong>OpenID Connect<\/strong><\/li>\n<li>Root URL: <code>http:\/\/localhost:8080<\/code><\/li>\n<li>Click <strong>Save<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>Under <code>Client Authentication<\/code>, set:\n<ul>\n<li>Access Type: <code>confidential<\/code><\/li>\n<li>Enable <code>Service Accounts<\/code><\/li>\n<li>Save the <strong>Client Secret<\/strong> (You'll use this in Spring Boot)<\/li>\n<\/ul>\n<\/li>\n<li>Create a new <strong>User<\/strong>:\n<ul>\n<li>Go to <code>Users &gt; Create<\/code><\/li>\n<li>Set username\/password (<code>testuser\/testpassword<\/code>)<\/li>\n<li>Assign a role (<code>user<\/code>)<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<hr \/>\n<h2><strong>2. Spring Boot Project Setup<\/strong><\/h2>\n<h3><strong>Add Dependencies (Maven)<\/strong><\/h3>\n<p>Add the required dependencies for Keycloak and Spring Security:<\/p>\n<pre><code class=\"language-xml\">&lt;dependencies&gt;\n    &lt;!-- Spring Boot Web --&gt;\n    &lt;dependency&gt;\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n        &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;\n    &lt;\/dependency&gt;\n\n    &lt;!-- Spring Security with OAuth2 --&gt;\n    &lt;dependency&gt;\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n        &lt;artifactId&gt;spring-boot-starter-oauth2-resource-server&lt;\/artifactId&gt;\n    &lt;\/dependency&gt;\n\n    &lt;!-- Spring Boot Security --&gt;\n    &lt;dependency&gt;\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n        &lt;artifactId&gt;spring-boot-starter-security&lt;\/artifactId&gt;\n    &lt;\/dependency&gt;\n\n    &lt;!-- Keycloak Spring Security Adapter --&gt;\n    &lt;dependency&gt;\n        &lt;groupId&gt;org.keycloak&lt;\/groupId&gt;\n        &lt;artifactId&gt;keycloak-spring-boot-starter&lt;\/artifactId&gt;\n        &lt;version&gt;22.0.1&lt;\/version&gt;\n    &lt;\/dependency&gt;\n&lt;\/dependencies&gt;<\/code><\/pre>\n<hr \/>\n<h2><strong>3. Configure <code>application.yml<\/code><\/strong><\/h2>\n<pre><code class=\"language-yaml\">server:\n  port: 8081  # Run on a different port than Keycloak\n\nspring:\n  security:\n    oauth2:\n      resourceserver:\n        jwt:\n          issuer-uri: http:\/\/localhost:8080\/realms\/myrealm\n  application:\n    name: my-secure-app\n\nkeycloak:\n  realm: myrealm\n  auth-server-url: http:\/\/localhost:8080\n  resource: myclient\n  credentials:\n    secret: YOUR_CLIENT_SECRET\n  bearer-only: true  # This means it will act as a Resource Server<\/code><\/pre>\n<hr \/>\n<h2><strong>4. Secure API Endpoints<\/strong><\/h2>\n<p>Create a secured REST API with role-based access:<\/p>\n<pre><code class=\"language-java\">package com.example.demo.controller;\n\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(&quot;\/api&quot;)\npublic class SecureController {\n\n    @GetMapping(&quot;\/public&quot;)\n    public String publicEndpoint() {\n        return &quot;This is a public endpoint&quot;;\n    }\n\n    @GetMapping(&quot;\/user&quot;)\n    @PreAuthorize(&quot;hasAuthority(&#039;ROLE_user&#039;)&quot;)\n    public String userEndpoint() {\n        return &quot;Hello, User! You have access to this protected endpoint.&quot;;\n    }\n\n    @GetMapping(&quot;\/admin&quot;)\n    @PreAuthorize(&quot;hasAuthority(&#039;ROLE_admin&#039;)&quot;)\n    public String adminEndpoint() {\n        return &quot;Hello, Admin! You have elevated access.&quot;;\n    }\n}<\/code><\/pre>\n<hr \/>\n<h2><strong>5. Configure Security<\/strong><\/h2>\n<p>Create a security config class:<\/p>\n<pre><code class=\"language-java\">package com.example.demo.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\nimport org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;\nimport org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;\nimport org.springframework.security.web.SecurityFilterChain;\n\n@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n    @Bean\n    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -&gt; auth\n                .requestMatchers(&quot;\/api\/public&quot;).permitAll()\n                .requestMatchers(&quot;\/api\/user&quot;).hasRole(&quot;user&quot;)\n                .requestMatchers(&quot;\/api\/admin&quot;).hasRole(&quot;admin&quot;)\n                .anyRequest().authenticated()\n            )\n            .oauth2ResourceServer(oauth2 -&gt; oauth2.jwt(jwt -&gt; jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));\n        return http.build();\n    }\n\n    @Bean\n    public JwtAuthenticationConverter jwtAuthenticationConverter() {\n        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();\n        grantedAuthoritiesConverter.setAuthorityPrefix(&quot;ROLE_&quot;); \/\/ Keycloak uses roles without &quot;ROLE_&quot; prefix\n        grantedAuthoritiesConverter.setAuthoritiesClaimName(&quot;realm_access.roles&quot;);\n\n        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();\n        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);\n        return jwtAuthenticationConverter;\n    }\n}<\/code><\/pre>\n<hr \/>\n<h2><strong>6. Test the Security<\/strong><\/h2>\n<h3><strong>Public API (No Token Needed)<\/strong><\/h3>\n<pre><code class=\"language-sh\">curl http:\/\/localhost:8081\/api\/public<\/code><\/pre>\n<h3><strong>Protected API (Requires Token)<\/strong><\/h3>\n<ol>\n<li>Get an access token from Keycloak:\n<pre><code class=\"language-sh\">export TOKEN=$(curl -X POST &#039;http:\/\/localhost:8080\/realms\/myrealm\/protocol\/openid-connect\/token&#039; \\\n -H &#039;Content-Type: application\/x-www-form-urlencoded&#039; \\\n -d &#039;client_id=myclient&#039; \\\n -d &#039;client_secret=YOUR_CLIENT_SECRET&#039; \\\n -d &#039;username=testuser&#039; \\\n -d &#039;password=testpassword&#039; \\\n -d &#039;grant_type=password&#039; | jq -r .access_token)<\/code><\/pre>\n<\/li>\n<li>Access the protected <code>\/user<\/code> endpoint:\n<pre><code class=\"language-sh\">curl -H &quot;Authorization: Bearer $TOKEN&quot; http:\/\/localhost:8081\/api\/user<\/code><\/pre>\n<\/li>\n<li>Try accessing the <code>\/admin<\/code> endpoint:\n<pre><code class=\"language-sh\">curl -H &quot;Authorization: Bearer $TOKEN&quot; http:\/\/localhost:8081\/api\/admin<\/code><\/pre>\n<p>If your user doesn't have the <code>admin<\/code> role, it should return <strong>403 Forbidden<\/strong>.<\/p>\n<\/li>\n<\/ol>\n<hr \/>\n<h2><strong>Conclusion<\/strong><\/h2>\n<p>This setup integrates <strong>Spring Boot<\/strong> with <strong>Keycloak<\/strong> using <strong>OIDC<\/strong> to protect APIs:<\/p>\n<ul>\n<li><strong>Keycloak<\/strong> is the authentication provider.<\/li>\n<li><strong>Spring Security<\/strong> handles authentication via JWT.<\/li>\n<li><strong>Role-based access<\/strong> is configured via <code>@PreAuthorize<\/code>.<\/li>\n<\/ul>\n<p>Would you like help setting up a frontend client for login? \ud83d\ude80<\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Setup Keycloak Before you start coding, make sure you have a running Keycloak instance: Run Keycloak using Docker docker run -d &#8211;name keycloak -p 8080:8080 \\ -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \\ quay.io\/keycloak\/keycloak:latest start-dev Create a Realm and Client Open Keycloak at http:\/\/localhost:8080\/ Login with admin\/admin Create a new Realm (e.g., myrealm) Under the Realm, [&hellip;] <a class=\"read-more\" href=\"https:\/\/www.fanyamin.com\/wordpress\/?p=1862\" title=\"Permanent Link to: Use OIDC and keycloak to protect your API\">&rarr;Read&nbsp;more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-1862","post","type-post","status-publish","format-standard","hentry","category-5"],"_links":{"self":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1862"}],"collection":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1862"}],"version-history":[{"count":1,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1862\/revisions"}],"predecessor-version":[{"id":1866,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1862\/revisions\/1866"}],"wp:attachment":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1862"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1862"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1862"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}