{"id":24866,"date":"2024-02-13T10:53:54","date_gmt":"2024-02-13T09:53:54","guid":{"rendered":"https:\/\/www.angulararchitects.io\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/"},"modified":"2024-05-16T23:26:25","modified_gmt":"2024-05-16T21:26:25","slug":"oauth2-with-spring-angular-keycloak-spring-for-resource-server","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/","title":{"rendered":"OAuth 2 with Spring, Angular, Keycloak &#8211; Spring for Resource Server"},"content":{"rendered":"<h2>1. Intro<\/h2>\n<p>This article covers the integration of OAuth2 into a Single Page Application (SPA) where Spring is the backend and Angular is the frontend. Keycloak, the common choice in the Java ecosystem, will take over the role of the Authorization server.<\/p>\n<p>The reader is not required to know about OAuth2 but should be familiar with Spring and Angular.<\/p>\n<p>OAuth2 is a de facto standard on how to authorize users. Bluntly put, authorization means that the application knows what the user is allowed to do and what not.<\/p>\n<p>To know with whom the application is communicating, the user authenticates themselves.<\/p>\n<p>From the specification perspective, it is a wrong statement, but in practice, we use OAuth2 quite often for both authentication and authorization.<\/p>\n<p>This complete walkthrough will explain the setup of Keykloak and the necessary implementation in Spring and Angular.<\/p>\n<p>The OAuth2 standard provides different constellations depending on the context. For example, mobile applications require a different setup than a Micro-Services architecture.<\/p>\n<p>We will follow the current best practices as of OAuth 2.1, which is still in a draft version at the time of this writing.<\/p>\n<h2>2. OAuth 2 in a Nutshell<\/h2>\n<p>OAuth 2 differentiates between different roles. In our use case, Spring takes on the Resource Server role. The main OAuth 2 communication happens between Angular and Keycloak. Angular's OAuth 2 role name is &quot;Client&quot; and Keycloak is the &quot;Authentication Server&quot;.<\/p>\n<p>As &quot;Client&quot;, Angular's task is to initiate the authorization. It sends the end user to Keycloak and includes some metadata so that Keycloak knows from which client the user came.<\/p>\n<p>Keycloak provides a login screen to the user where they type in their credentials. This has the nice side effect, that neither Angular nor Spring need to know or store the credentials. Also, the user must consent to allowing the client to act on their behalf.<\/p>\n<p>After a bit of &quot;chit-chat&quot; between Angular and Keyckloak, the Angular client finally receives an access token that allows to talk with the resource server on behalf of the user. However, the Acess Token does not inform the client about the user. <\/p>\n<p>For this reason, OpenId Connect (OIDC) was defined. It runs on top of OAuth 2, and Keycloak provides the client with user information by also issung an Id Token. It's a JSON Web Token (JWT) that contains so called &quot;claims&quot;.<\/p>\n<p>That &quot;chit-chat&quot; is everything else casual but highly standardized and has security concerns as a top priority. As daunting as it might be, never implement this flow on your own, but use - as in this article - the authorization server's built-in library or an OAuth2-validated generic library like <a href=\"https:\/\/github.com\/manfredsteyer\/angular-oauth2-oidc\"><a href=\"https:\/\/github.com\/manfredsteyer\/angular-oauth2-oidc\">https:\/\/github.com\/manfredsteyer\/angular-oauth2-oidc<\/a><\/a>.<\/p>\n<p>In a further article, we discuss how to increase security by using OAuth 2, OIDC and the issued tokes <a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/part-1-the-problem-with-security-tokens-in-the-browser\/\">only at the server-side behind a Gateway<\/a>.<\/p>\n<p>Whenever Angular sends an HTTP request to Spring, it adds the JWT to the HTTP header. Spring parses the JWT and maps it into Spring Security. Spring has to be aware and needs to trust the Keycloak server. Therefore, Spring fetches the public key or keyset from Keycloak and uses them to verify that the signature of the JWT is valid. As it is common in asymmetric encryption, Keycloak signs every JWT with its private key.<\/p>\n<h2>3. Keycloak Setup<\/h2>\n<h3>3.1. Realms<\/h3>\n<p>The fastest way to run Keycloak is to use Docker. This would be the content for <strong>docker-compose.yml<\/strong>, which starts Keycloak with an admin user <strong>eternal<\/strong> and password <strong>eternal123<\/strong>.<\/p>\n<pre><code class=\"language-yml\">oauth2:\n  image: quay.io\/keycloak\/keycloak:22.0.5\n  command:\n    - start-dev\n  environment:\n    - KEYCLOAK_ADMIN=eternal\n    - KEYCLOAK_ADMIN_PASSWORD=eternal123\n  ports:\n    - &quot;8081:8080&quot;<\/code><\/pre>\n<p>An alternative is to start it directly from the command line:<\/p>\n<pre><code class=\"language-bash\">docker run -it -p &quot;8081:8080&quot; -e &#039;KEYCLOAK_ADMIN=eternal&#039; -e &#039;KEYCLOAK_ADMIN_PASSWORD=eternal123&#039; quay.io\/keycloak\/keycloak:21.0.2 start-dev<\/code><\/pre>\n<p>Navigate to <a href=\"http:\/\/localhost:8081\/admin\"><a href=\"http:\/\/localhost:8081\/admin\">http:\/\/localhost:8081\/admin<\/a><\/a>. You should see Keycloak's login screen. Enter <strong>eternal<\/strong> as username and <strong>eternal123<\/strong> as password. This should lead you to the Administration UI.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Login_Screen.png\" alt=\"Keycloak Login Screen\"><\/p>\n<p><em>Keycloak Login<\/em><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Admin_Screen.png\" alt=\"Admin Screen\"><\/p>\n<p><em>Keycloak Admin Screen<\/em><\/p>\n<p>The dropdown in the top left shows a list of existing realms. You see that as isolated instances of Keycloak with their own users, claims, etc.<\/p>\n<p>If you are familiar with databases, it is the same concept. You run one Oracle, SQL Server, etc., and can set up multiple databases.<\/p>\n<p>Click on the Realm's dropdown and create a new realm named &quot;eternal.&quot;<\/p>\n<h3>3.2. Client Setup<\/h3>\n<p>To create a new client, select &quot;Clients&quot; from the menu. The Client Type should show &quot;OpenID Connect&quot; as the default value in the form. That's what we need.<\/p>\n<p>The second mandatory field is the client ID. You can choose whatever you find fit. In our use case, we decided to use &quot;eternal.&quot;<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Client_Setup_1.png\" alt=\"Client Setup 1\"><\/p>\n<p><em>Client Setup 1<\/em><\/p>\n<p>In the second step, we need to provide detailed information. We disable &quot;Client authentication.&quot; This is an OIDC setting under the name &quot;public access type.&quot; That means that as the client and running in a browser, Angular can directly communicate with Keycloak.<\/p>\n<p>&quot;Direct access grants&quot; is disabled. This setting would allow Angular to ask for the username and password and send it to Keycloak. Angular should never possess these credentials. That is also in accordance with the &quot;OAuth2 Best Practices&quot;.<\/p>\n<p>We can leave the rest of the form fields as they are. So, there is no &quot;Implicit Flow&quot; or &quot;OAuth 2.0 Device Authorization grant&quot;. These are settings which increase the risk of a security breach.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Client_Setup_2.png\" alt=\"Client Setup 2\"><\/p>\n<p><em>Client Setup 2<\/em><\/p>\n<p>Next, we define the URL of Angular. Since Angular sends the user to Keycloak and Keycloak should send the user back to Angular, we will use &quot;<a href=\"http:\/\/localhost:4200\/\"><a href=\"http:\/\/localhost:4200\/\">http:\/\/localhost:4200\/<\/a><\/a>&quot; (mind the ending slash) for both &quot;Valid redirect URIs&quot; and &quot;Web origins.&quot;<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Client_Setup_3.png\" alt=\"Client Setup 3\"><\/p>\n<p><em>Client Setup 3<\/em><\/p>\n<h3>3.3. User Setup<\/h3>\n<p>We create two users:<\/p>\n<ul>\n<li>John List: An authenticated customer who can view holidays.<\/li>\n<li>Lucy Sanders: An administrator who can edit holidays.<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Create_User.png\" alt=\"Create User\"><\/p>\n<p><em>Create User<\/em><\/p>\n<p>We also create two client roles, i.e., they only apply to our Eternal application.<\/p>\n<p>These are:<\/p>\n<ul>\n<li>view-holidays<\/li>\n<li>admin-holidays<\/li>\n<\/ul>\n<p>Navigate via &quot;Clients&quot; -&gt; Select &quot;eternal&quot; -&gt; &quot;Roles&quot; -&gt; &quot;Create role&quot;.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Create_Role.png\" alt=\"Create Role\"><\/p>\n<p><em>Create Role<\/em><\/p>\n<p>Since we don't want to assign these roles directly to our users, we create two groups:<\/p>\n<ul>\n<li>customer<\/li>\n<li>admin<\/li>\n<\/ul>\n<p>Navigate via &quot;Groups&quot; -&gt; &quot;Create Group&quot;.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Create_Group.png\" alt=\"Create Group\"><\/p>\n<p><em>Create Group<\/em><\/p>\n<p>To assign the client roles to groups<\/p>\n<p>Finally, we assign the user &quot;John List&quot; to the group &quot;customer&quot;, and &quot;Lucy Sanders&quot; to &quot;admin&quot;.<\/p>\n<p>Navigate via &quot;Groups&quot; -&gt; Select &quot;customer&quot; -&gt; &quot;Role Mapping&quot; -&gt; &quot;Assign Role&quot; -&gt; Select &quot;Filter by clients&quot; instead &quot;Filter by realm roles&quot; -&gt; Search for &quot;eternal&quot;.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/Assign_Role.png\" alt=\"Assign Role\"><\/p>\n<p><em>Assign Role<\/em><\/p>\n<h2>4. Angular as OAuth2 Client<\/h2>\n<p>Angular should connect to Keycloak. We install the official Keycloak library for Javascript via<\/p>\n<pre><code>npm i keycloak-js@22.0.5<\/code><\/pre>\n<p>It is crucial that the version of the npm packages matches one of the server instances (see <strong>docker-compose.yml<\/strong>).<\/p>\n<p>Specific community-based libraries for Angular are available as well. Since only a few lines of TypeScript are necessary, we stay with the JavaScript version.<\/p>\n<h3>4.1. <code>KeycloakService<\/code>: Wrapping &quot;keycloak-js&quot;<\/h3>\n<p>We create a <code>KeycloakService<\/code> which acts as a wrapper:<\/p>\n<pre><code class=\"language-typescript\">import { Injectable } from &quot;@angular\/core&quot;;\nimport Keycloak from &quot;keycloak-js&quot;;\n\nexport interface UserProfile {\n  sub: string;\n  email: string;\n  given_name: string;\n  family_name: string;\n  token: string;\n}\n\n@Injectable({ providedIn: &quot;root&quot; })\nexport class KeycloakService {\n  _keycloak: Keycloak | undefined;\n  profile: UserProfile | undefined;\n\n  get keycloak() {\n    if (!this._keycloak) {\n      this._keycloak = new Keycloak({\n        url: &quot;http:\/\/localhost:8081&quot;,\n        realm: &quot;eternal&quot;,\n        clientId: &quot;eternal&quot;,\n      });\n    }\n    return this._keycloak;\n  }\n\n  async init() {\n    const authenticated = await this.keycloak.init({\n      onLoad: &quot;check-sso&quot;,\n      silentCheckSsoRedirectUri:\n        window.location.origin + &quot;\/assets\/silent-check-sso.html&quot;,\n    });\n\n    if (!authenticated) {\n      return authenticated;\n    }\n    this.profile =\n      (await this.keycloak.loadUserInfo()) as unknown as UserProfile;\n    this.profile.token = this.keycloak.token || &quot;&quot;;\n\n    return true;\n  }\n\n  login() {\n    return this.keycloak.login();\n  }\n\n  logout() {\n    return this.keycloak.logout({ redirectUri: &quot;http:\/\/localhost:8081&quot; });\n  }\n}<\/code><\/pre>\n<p><code>KeycloakService<\/code> exposes an instance of <code>keycloak<\/code>. As soon as there is access to that property, the service generates the instance and stores it as Singleton. To instantiate, we need to provide the values for the realm, the ID of the client, and Keycloak's URL.<\/p>\n<p>The first access to the <code>keycloak<\/code> property happens in the <code>KeycloakService::init<\/code>. First, there runs a check if the user is already signed in from a former session. Second, we need to define an HTML file for the silent SSO. That one is required to automatically re-login, once the current JWT is about to expire.<\/p>\n<p>The re-login would require forwarding to Keycloak. &quot;keycloak-js&quot; runs this redirection inside the iframe, so the user is unaware of it. The user doesn't need to enter their credentials because Keycloak adds an HTTP-only cookie, which allows it to recognize the user.<\/p>\n<p><strong>silent-check-sso.html<\/strong> is very small:<\/p>\n<pre><code class=\"language-html\">&lt;html&gt;\n  &lt;body&gt;\n    &lt;script&gt;\n      parent.postMessage(location.href, location.origin);\n    &lt;\/script&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<p><code>KeycloakService::init<\/code> also sets the user's profile. Due to OIDC, the fields are standardized. We need to add the JWT as an HTTP header whenever we send a request to Spring. Because of that, we add the JWT itself to the profile.<\/p>\n<p>Finally, <code>KeycloakService<\/code> exposes methods for the login and logout.<\/p>\n<p>For simplicity, we didn't load the credentials from an Angular environment file. In real life, this is exactly what we should do.<\/p>\n<h3>4.2: <code>KeycloakStore<\/code> - Stateful service.<\/h3>\n<p>The next step is to use the <code>KeycloakService<\/code> inside a stateful service. We want to have Signals that change whenever the user is logged in or logged out.<\/p>\n<pre><code class=\"language-typescript\">@Injectable({ providedIn: &quot;root&quot; })\nexport class SecurityStore {\n  #keycloakService = inject(KeycloakService);\n\n  loaded = signal(false);\n  user = signal&lt;User | undefined&gt;(undefined);\n\n  loadedUser = computed(() =&gt; (this.loaded() ? this.user() : undefined));\n  signedIn = computed(() =&gt; this.loaded() &amp;&amp; !this.user()?.anonymous);\n\n  constructor() {\n    this.onInit();\n  }\n\n  async onInit() {\n    const isServer = isPlatformServer(inject(PLATFORM_ID));\n    const keycloakService = inject(KeycloakService);\n    if (isServer) {\n      this.user.set(ANONYMOUS_USER);\n      this.loaded.set(true);\n      return;\n    }\n\n    const isLoggedIn = await keycloakService.init();\n    if (isLoggedIn &amp;&amp; keycloakService.profile) {\n      const { sub, email, given_name, family_name, token } =\n        keycloakService.profile;\n      const user = {\n        id: sub,\n        email,\n        name: <code>${given_name} ${family_name}<\/code>,\n        anonymous: false,\n        bearer: token,\n      };\n      this.user.set(user);\n      this.loaded.set(true);\n    } else {\n      this.user.set(ANONYMOUS_USER);\n      this.loaded.set(true);\n    }\n  }\n\n  async signIn() {\n    await this.#keycloakService.login();\n  }\n\n  async signOut() {\n    await this.#keycloakService.logout();\n  }\n}<\/code><\/pre>\n<p><code>SecurityStore<\/code> uses the <code>KeycloakService<\/code> as its proxy for the rest of the application.<\/p>\n<p>As such, it also can change some terms to fit the application's domain model. For example, &quot;login&quot; becomes &quot;signIn&quot;, the same with &quot;logout&quot;. <code>SecurityStore<\/code> also maps the OIDC properties to <code>User<\/code> with slightly different names.<\/p>\n<p>Additionally, it provides <code>loadedUser<\/code> and <code>signedId<\/code>, which are computed Signals.<\/p>\n<p>This version already deals with Server-Side Rendinger (SSR). If the application runs on a server, getting credentials from the user is impossible. So, if it runs on the server, we return to the default <code>ANONYMOUS_USER<\/code>.<\/p>\n<h3><code>securityInterceptor<\/code>: JWT as HTTP Header<\/h3>\n<p>To include the JWT in every request, we use an interceptor function.<\/p>\n<pre><code class=\"language-typescript\">export const securityInterceptor: HttpInterceptorFn = (req, next) =&gt; {\n  const keycloakService = inject(SecurityStore);\n\n  const bearer = keycloakService.user()?.bearer;\n\n  if (!bearer) {\n    return next(req);\n  }\n\n  return next(\n    req.clone({\n      headers: req.headers.set(&quot;Authorization&quot;, <code>Bearer ${bearer}<\/code>),\n    })\n  );\n};<\/code><\/pre>\n<p><code>securityInterceptor<\/code> is straightforward. It checks if <code>user<\/code> of <code>SecurityStore<\/code> has a valid JWT (<code>bearer<\/code> property) and, if yes, adds that one as a header to the current request.<\/p>\n<p>For larger applications that communicate with different endpoints, one might also check for the domain itself before sending everyone the JWT.<\/p>\n<h3>Login Button: Bringing it all together<\/h3>\n<p>Our application shows the login button in its header. The <code>HeaderComponent<\/code> injects the <code>SecurityStore<\/code> and comes with the following template:<\/p>\n<pre><code class=\"language-html\">&lt;div class=&quot;security&quot;&gt;\n  @if (user(); as userValue) { @if (userValue.anonymous) {\n  &lt;button\n    data-testid=&quot;btn-sign-in&quot;\n    mat-raised-button\n    (click)=&quot;securityStore.signIn()&quot;\n  &gt;\n    Sign In\n  &lt;\/button&gt;\n  } @else {\n  &lt;div class=&quot;flex items-center&quot;&gt;\n    &lt;p class=&quot;profile&quot; data-testid=&quot;p-username&quot;&gt;Welcome {{ userValue.name }}&lt;\/p&gt;\n    &lt;button\n      (click)=&quot;securityStore.signOut()&quot;\n      data-testid=&quot;btn-sign-out&quot;\n      mat-raised-button\n    &gt;\n      Sign Out\n    &lt;\/button&gt;\n  &lt;\/div&gt;\n  } }\n&lt;\/div&gt;<\/code><\/pre>\n<p>The template toggles between the &quot;Sign In&quot; and &quot;Sign Out&quot; buttons, depending on the current user's state.<\/p>\n<p>Try it out! Start Angular and click on &quot;Sign In&quot;. You should find yourself at the login screen of Keycloak. Type in the credentials for user &quot;Jost List&quot;, and Keycloak should redirect you to Angular.<\/p>\n<p>Open the network tab. You should see that the request to the holiday's endpoint also contains the new HTTP header, which contains the JWT.<\/p>\n<p>You can take a look at the JWT data. <a href=\"https:\/\/jwt.io\/\"><a href=\"https:\/\/jwt.io\/\">https:\/\/jwt.io\/<\/a><\/a> provides an online service that parses it.<\/p>\n<h2>5. Spring as Resource<\/h2>\n<p>We have to install the necessary dependencies to configure Spring as an OAuth Resource server.<\/p>\n<p>Our application is using Gradle Therefore, we add the following to the <code>dependencies<\/code>:<\/p>\n<pre><code class=\"language-groovy\">dependencies {\n  implementation &#039;org.springframework.boot:spring-boot-starter-oauth2-resource-server&#039;\n}<\/code><\/pre>\n<p>The next step is the registration of the Keycloak instance. We can use the <strong>application.yml<\/strong> for that to add the URI:<\/p>\n<pre><code class=\"language-yml\">spring:\n  security:\n    oauth2:\n      resourceserver:\n        jwt:\n          issuer-uri: &quot;http:\/\/localhost:8081\/realms\/eternal&quot;<\/code><\/pre>\n<p>Spring will now connect to Keycloak, request its keys, and use them to verify any JWT that Angular sends.<\/p>\n<p>The next step is to configure Spring Security and integrate OAuth2.<\/p>\n<p>For that, we create a <strong>SecurityConfiguration.java<\/strong>, which contains the necessary setup:<\/p>\n<pre><code class=\"language-java\">@Configuration\npublic class SecurityConfiguration {\n\n  @Bean\n  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n    http\n      .cors()\n      .configurationSource(request -&gt; {\n        var cors = new CorsConfiguration();\n        cors.setAllowedOrigins(List.of(&quot;http:\/\/localhost:4200&quot;));\n        cors.setAllowedMethods(\n          List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;OPTIONS&quot;, &quot;PUT&quot;, &quot;DELETE&quot;)\n        );\n        cors.setAllowedHeaders(List.of(&quot;*&quot;));\n        cors.setAllowCredentials(true);\n\n        return cors;\n      })\n      .and()\n      .authorizeHttpRequests(authorize -&gt; {\n        try {\n          authorize\n            .anyRequest()\n            .authenticated()\n            .and()\n            .oauth2ResourceServer()\n            .jwt()\n            .jwtAuthenticationConverter(\n              new KeycloakJwtAuthenticationConverter(List.of(&quot;account&quot;))\n            );\n        } catch (Exception e) {\n          throw new RuntimeException(e);\n        }\n      });\n    return http.build();\n  }\n}<\/code><\/pre>\n<p>Next to the obligatory configuration of CORS, we use <code>authorizeHttpRequests<\/code> and demand that any request be authenticated.<\/p>\n<p>We also configure a converter, which will map the roles in the JWT to Spring Security's model.<\/p>\n<p>The Converter looks very simple:<\/p>\n<p><strong>KeycloakJwtAuthenticationConverter.java<\/strong><\/p>\n<pre><code class=\"language-java\">public class KeycloakJwtAuthenticationConverter\n    implements Converter&lt;Jwt, AbstractAuthenticationToken&gt; {\n  @Override\n  public AbstractAuthenticationToken convert(Jwt source) {\n    return new JwtAuthenticationToken(\n        source,\n        Stream.concat(\n                new JwtGrantedAuthoritiesConverter().convert(source).stream(),\n                extractResourceRoles(source).stream())\n            .collect(toSet()));\n  }\n\n  private Collection&lt;? extends GrantedAuthority&gt; extractResourceRoles(Jwt jwt) {\n    var resourceAccess = new HashMap&lt;&gt;(jwt.getClaim(&quot;resource_access&quot;));\n\n    var eternal = (Map&lt;String, List&lt;String&gt;&gt;) resourceAccess.get(&quot;eternal&quot;);\n\n    var roles = (ArrayList&lt;String&gt;) eternal.get(&quot;roles&quot;);\n\n    return roles.stream()\n        .map(role -&gt; new SimpleGrantedAuthority(&quot;ROLE_&quot; + role.replace(&quot;-&quot;, &quot;_&quot;)))\n        .collect(toSet());\n  }\n}<\/code><\/pre>\n<p>From the JWT, we fetch the claim &quot;resource_access&quot; and explicitly those for our client &quot;eternal&quot;.<\/p>\n<p>Since we only want to migrate the roles to Spring Security, we get the &quot;roles&quot; and instantiate a new <code>SimpleGrantedAuthority<\/code> for each role.<\/p>\n<p>This allows us to refer to the for example, via <a href=\"mailto:code&gt;@Secured&lt;\/code\">code>@Secured<\/code<\/a>.<\/p>\n<p>We add it to the endpoint of the <code>HolidaysController<\/code>, which is responsible for adding a new entry. The required role is &quot;ROLE_ADMIN_HOLIDAYS.&quot;<\/p>\n<p>Restart Spring. In Angular, make sure you are signed in as &quot;John List&quot; and click on &quot;Add Holiday&quot;. You should see that the server responds with an error code of 401, meaning the request is unauthorized.<\/p>\n<p>Next, sign in as &quot;Lucy Sanders&quot; and try again to add a new holiday. This time, it should work.<\/p>\n<h2>6. Summary<\/h2>\n<p>This article has shown a minimalistic but secure setup that integrates OAuth2 into a classic SPA with Angular and Spring.<\/p>\n<p>The repository is available at: <a href=\"https:\/\/github.com\/rainerhahnekamp\/eternal\/tree\/article\/2024-02-14-oauth2-spring-resource-angular-client\"><a href=\"https:\/\/github.com\/rainerhahnekamp\/eternal\/tree\/article\/2024-02-14-oauth2-spring-resource-angular-client\">https:\/\/github.com\/rainerhahnekamp\/eternal\/tree\/article\/2024-02-14-oauth2-spring-resource-angular-client<\/a><\/a><\/p>\n<p>Spring acted as a resource server, meaning the front end contains the JWT.<\/p>\n<p>The other approach is that Spring takes over the OAuth2 &quot;Client&quot; role. In that setting, Spring sets a simple, old-fashioned cookie in Angular, which is HTTP-only.<\/p>\n<p>That setup requires more configuration on Spring's side, which becomes complicated if we deal with a microservices architecture.<\/p>\n<p>Either way, an upcoming article will discuss the Client Role with Spring.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Intro This article covers the integration of OAuth2 into a Single Page Application (SPA) where Spring is the backend and Angular is the frontend. Keycloak, the common choice in the Java ecosystem, will take over the role of the Authorization server. The reader is not required to know about OAuth2 but should be familiar [&hellip;]<\/p>\n","protected":false},"author":13,"featured_media":24864,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-24866","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>OAuth 2 with Spring, Angular, Keycloak - Spring for Resource Server - ANGULARarchitects<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"OAuth 2 with Spring, Angular, Keycloak - Spring for Resource Server - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"1. Intro This article covers the integration of OAuth2 into a Single Page Application (SPA) where Spring is the backend and Angular is the frontend. Keycloak, the common choice in the Java ecosystem, will take over the role of the Authorization server. The reader is not required to know about OAuth2 but should be familiar [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2024-02-13T09:53:54+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-05-16T21:26:25+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1000\" \/>\n\t<meta property=\"og:image:height\" content=\"750\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Rainer Hahnekamp\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Rainer Hahnekamp\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\"},\"author\":{\"name\":\"Rainer Hahnekamp\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/652ef3ff751c052d51f2640fecdfa30e\"},\"headline\":\"OAuth 2 with Spring, Angular, Keycloak &#8211; Spring for Resource Server\",\"datePublished\":\"2024-02-13T09:53:54+00:00\",\"dateModified\":\"2024-05-16T21:26:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\"},\"wordCount\":1916,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\",\"name\":\"OAuth 2 with Spring, Angular, Keycloak - Spring for Resource Server - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg\",\"datePublished\":\"2024-02-13T09:53:54+00:00\",\"dateModified\":\"2024-05-16T21:26:25+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg\",\"width\":1000,\"height\":750},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"OAuth 2 with Spring, Angular, Keycloak &#8211; Spring for Resource Server\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/652ef3ff751c052d51f2640fecdfa30e\",\"name\":\"Rainer Hahnekamp\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/4afdaa50c6ae20dbdcb5c5f62e0070b02b059a76cb63e56f22c3475fbb320890?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/4afdaa50c6ae20dbdcb5c5f62e0070b02b059a76cb63e56f22c3475fbb320890?s=96&d=mm&r=g\",\"caption\":\"Rainer Hahnekamp\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"OAuth 2 with Spring, Angular, Keycloak - Spring for Resource Server - ANGULARarchitects","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/","og_locale":"en_US","og_type":"article","og_title":"OAuth 2 with Spring, Angular, Keycloak - Spring for Resource Server - ANGULARarchitects","og_description":"1. Intro This article covers the integration of OAuth2 into a Single Page Application (SPA) where Spring is the backend and Angular is the frontend. Keycloak, the common choice in the Java ecosystem, will take over the role of the Authorization server. The reader is not required to know about OAuth2 but should be familiar [&hellip;]","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/","og_site_name":"ANGULARarchitects","article_published_time":"2024-02-13T09:53:54+00:00","article_modified_time":"2024-05-16T21:26:25+00:00","og_image":[{"width":1000,"height":750,"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg","type":"image\/jpeg"}],"author":"Rainer Hahnekamp","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Rainer Hahnekamp","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/"},"author":{"name":"Rainer Hahnekamp","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/652ef3ff751c052d51f2640fecdfa30e"},"headline":"OAuth 2 with Spring, Angular, Keycloak &#8211; Spring for Resource Server","datePublished":"2024-02-13T09:53:54+00:00","dateModified":"2024-05-16T21:26:25+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/"},"wordCount":1916,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/","name":"OAuth 2 with Spring, Angular, Keycloak - Spring for Resource Server - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg","datePublished":"2024-02-13T09:53:54+00:00","dateModified":"2024-05-16T21:26:25+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2024\/02\/shutterstock_2368460999.jpg","width":1000,"height":750},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/oauth2-with-spring-angular-keycloak-spring-for-resource-server\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"OAuth 2 with Spring, Angular, Keycloak &#8211; Spring for Resource Server"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/652ef3ff751c052d51f2640fecdfa30e","name":"Rainer Hahnekamp","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/4afdaa50c6ae20dbdcb5c5f62e0070b02b059a76cb63e56f22c3475fbb320890?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/4afdaa50c6ae20dbdcb5c5f62e0070b02b059a76cb63e56f22c3475fbb320890?s=96&d=mm&r=g","caption":"Rainer Hahnekamp"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/24866","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/13"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=24866"}],"version-history":[{"count":2,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/24866\/revisions"}],"predecessor-version":[{"id":25774,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/24866\/revisions\/25774"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/24864"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=24866"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=24866"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=24866"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}