Does securing a REST application with a JWT and Basic authentication make sense?

Here is some code to back up the accepted answer on how to do this in Spring....simply extend UsernamePasswordAuthenticationFilter and add it to Spring Security...this works well with HTTP Basic Authentication + Spring Security

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {

        this.authenticationManager = authenticationManager;

    }

    @Override

    public Authentication attemptAuthentication(HttpServletRequest req,

                                                HttpServletResponse res) throws AuthenticationException {

        try {

            ApplicationUser creds = new ObjectMapper()

                    .readValue(req.getInputStream(), ApplicationUser.class);

            return authenticationManager.authenticate(

                    new UsernamePasswordAuthenticationToken(

                            creds.getUsername(),

                            creds.getPassword(),

                            new ArrayList<>())

            );

        } catch (IOException e) {

            throw new RuntimeException(e);

        }

    }

    @Override

    protected void successfulAuthentication(HttpServletRequest req,

                                            HttpServletResponse res,

                                            FilterChain chain,

                                            Authentication auth) throws IOException, ServletException {

        String token = Jwts.builder()

                .setSubject(((User) auth.getPrincipal()).getUsername())

                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))

                .signWith(SignatureAlgorithm.HS512, SECRET)

                .compact();

        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);

    }

}

using JWT lib.:

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

spring boot config class

package com.vanitysoft.payit.security.web.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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 com.vanitysoft.payit.util.SecurityConstants;

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         @Autowired
           private UserDetailsService userDetailsService;

            @Autowired
            private  BCryptPasswordEncoder bCryptPasswordEncoder;

         @Override
           protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(userDetailsService)
                      .passwordEncoder(bCryptPasswordEncoder);
           }

         @Override
            protected void configure(HttpSecurity http) throws Exception {
             http.cors().and().csrf().disable()
                    .authorizeRequests()                             
                        .antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
                        .antMatchers("/user/**").authenticated()
                        .and()
                        .httpBasic()
                        .and()
                        .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                        .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .and()
                        .logout()
                        .permitAll();

            }
    }

Assuming 100% TLS for all communication - both during and at all times after login - authenticating with username/password via basic authentication and receiving a JWT in exchange is a valid use case. This is almost exactly how one of OAuth 2's flows ('password grant') works.

The idea is that the end user is authenticated via one endpoint, e.g. /login/token using whatever mechanism you want, and the response should contain the JWT that is to be sent back on all subsequent requests. The JWT should be a JWS (i.e. a cryptographically signed JWT) with a proper JWT expiration (exp) field: this ensures that the client cannot manipulate the JWT or make it live longer than it should.

You don't need an X-Auth-Token header either: the HTTP Authentication Bearer scheme was created for this exact use case: basically any bit of information that trails the Bearer scheme name is 'bearer' information that should be validated. You just set the Authorization header:

Authorization: Bearer <JWT value here>

But, that being said, if your REST client is 'untrusted' (e.g. JavaScript-enabled browser), I wouldn't even do that: any value in the HTTP response that is accessible via JavaScript - basically any header value or response body value - could be sniffed and intercepted via MITM XSS attacks.

It's better to store the JWT value in a secure-only, http-only cookie (cookie config: setSecure(true), setHttpOnly(true)). This guarantees that the browser will:

  1. only ever transmit the cookie over a TLS connection and,
  2. never make the cookie value available to JavaScript code.

This approach is almost everything you need to do for best-practices security. The last thing is to ensure that you have CSRF protection on every HTTP request to ensure that external domains initiating requests to your site cannot function.

The easiest way to do this is to set a secure only (but NOT http only) cookie with a random value, e.g. a UUID.

Then, on every request into your server, ensure that your own JavaScript code reads the cookie value and sets this in a custom header, e.g. X-CSRF-Token and verify that value on every request in the server. External domain clients cannot set custom headers for requests to your domain unless the external client gets authorization via an HTTP Options request, so any attempt at a CSRF attack (e.g. in an IFrame, whatever) will fail for them.

This is the best of breed security available for untrusted JavaScript clients on the web today that we know of. Stormpath wrote an article on these techniques as well if you're curious. HTH!