'Double authentication error with JWT Spring Security and Angular 12

I'm trying to get my JWT token from spring security. I can get it from postman, but when I'm making the request from angular, it opens a snackBar requesting credentials.

This is my login service:

import { environment } from './../../environments/environment';
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class LoginService {
  url: string = `${environment.apiToken}`;

  constructor(private http: HttpClient, private router: Router) { }

  logIn(usuario: string, contrasena: string) {
    const body = `grant_type=password&username=${encodeURIComponent(usuario)}&password=${encodeURIComponent(contrasena)}`;

    console.log(btoa(environment.TOKEN_AUTH_USERNAME + ':' + environment.TOKEN_AUTH_PASSWORD));
    console.log(body);

    return this.http.post<any>(this.url, body, {
      headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
        .set('Authorization', 'Basic ' + btoa(environment.TOKEN_AUTH_USERNAME + ':' + environment.TOKEN_AUTH_PASSWORD))
    });
  }

  logOut() {
    if (this.estaLogueado()) {
      let token = sessionStorage.getItem(environment.TOKEN_NAME);

      this.http.get(`${environment.HOST}/cerrarSesion/anular/${token}`).subscribe(() => {
        sessionStorage.clear();
        this.router.navigate(['']);
      });
    }else{
      this.router.navigate(['']);
    }
  }

  isLoggedIn(): boolean {
    let token = sessionStorage.getItem(environment.TOKEN_NAME);
    return token !== null;
  }
}

I'm making requests by a proxy, this is my proxy.conf

{
    "/token": {
        "target": "http://localhost:8080/oauth/token",
        "secure": false,
        "logLevel":"debug"
    }
}

This is the snackbar This is the snackbar

When I'm checking the headers and body of the request made by angular, it's the same request from postman.

Authorization headers from angular Authorization headers from angular

Body from angular Body from angular

Authorization and headers from postman Authorization and headers from postman

This is my application.properties in Spring

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

spring.jpa.database=postgresql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost/demo
!<-- Database Credentials -->
spring.datasource.username=postgres
spring.datasource.password=admin

!<-- Security -->
security.oauth2.resource.filter-order=3
security.signing-key = MaYzkSjmkzPC57L
security.encoding-strength=256
security.security-realm= Spring Boot JWT
!<-- jwt -->
security.jwt.client-id=redsocialaappspring
security.jwt.client-secret=redsocial2021
security.jwt.grant-type=password
security.jwt.scope-read=read
security.jwt.scope-write=write
security.jwt.resource-ids=redsocialId

This is my securityConfig class

package com.example.demo;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer encodingStrength;

    @Value("${security.security-realm}")
    private String securityRealm;

    // Libreria para hashing
    @Autowired
    private BCryptPasswordEncoder bcrypt;

    // Obtiene usuarios, roles y claves de la aplicación
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    // Codificar las contraseñas por hashing
    @Bean
    public static BCryptPasswordEncoder passwordEnconder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    // Obtener detalles del usuario por medio de una interfaz
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bcrypt);
    }

    // Comportamiento de la seguridad, al ser por servicios es stateless
    // Desactivamos tokens csrf -- veitando ataques js dentro de los formularios
    // Usamos jwt -Oauth
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .httpBasic()
        .realmName(securityRealm)
        .and()
        .csrf()
        .disable();
    }

    // Instancia para crear tokens
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }

    // Guardado de tokens
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(this.dataSource);
    }

    // Acción primaria en ejecutarse
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setReuseRefreshToken(false);
        return defaultTokenServices;
    }

}

This is my resourceServerConfig class

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;

import com.example.demo.exception.AuthExceptionOwn;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
    
    //Traemos la configuración de SecurityConfig
    @Autowired
    private ResourceServerTokenServices tokenServices;
    
    @Value("${security.jwt.resource-ids}")
    private String resourceIds;
    
    //Creación de token y configuración de resourceIds
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception{
        resources.resourceId(resourceIds).tokenServices(tokenServices);
    }
    
    //URLs y metodos de proteccion
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http
        .exceptionHandling().authenticationEntryPoint(new AuthExceptionOwn())
        .and()
        .requestMatchers()
        .and()
        .authorizeRequests()
        .antMatchers("/amigos/**").authenticated()
        .antMatchers("/usuarios/**").permitAll()
        .antMatchers("/cerrarSesion/**").permitAll()
        .antMatchers("/token/**").permitAll();
    }

}

This is my authorizationServer class

package com.example.demo;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

//Elaboración del token
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter{
    
    @Value("${security.jwt.client-id}")
    private String clientId;

    @Value("${security.jwt.client-secret}")
    private String clientSecret;

    @Value("${security.jwt.grant-type}")
    private String grantType;
    
    @Value("${security.jwt.scope-read}")
    private String scopeRead;
    
    @Value("${security.jwt.scope-write}")
    private String scopeWrite;
    
    @Value("${security.jwt.resource-ids}")
    private String resourceIds;
    
    //Se trae la configuración
    @Autowired
    private TokenStore tokenStore;
    
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private BCryptPasswordEncoder bcrypt;
    
    //Grantype genera valores de acceso
    //Tiempo de vida del token
    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception{
        configurer.inMemory().withClient(clientId).secret(bcrypt.encode(clientSecret))
        .authorizedGrantTypes(grantType).scopes(scopeRead, scopeWrite).resourceIds(resourceIds)
        .accessTokenValiditySeconds(7200).refreshTokenValiditySeconds(0);
    }
    
    //Se genera la cadena de texto de jwt
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        
        enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        endpoints.tokenStore(tokenStore).accessTokenConverter(accessTokenConverter)
        .tokenEnhancer(enhancerChain).authenticationManager(authenticationManager);
    }

}


Solution 1:[1]

I've found the problem, I'm calling /token twice at the end of my angular proxy

The proxy.conf.json endded up like this

{
    "/token": {
        "target": "http://localhost:8080/oauth",
        "secure": false,
        "logLevel":"debug"
    }
}

Thanks everybody for your help >:[.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 tupapiriki27