Verify the ID token obtained from Firebase on the server side (Java + SpringBoot)

Introduction

You can get an ID token (JWT) when you log in using Firebase Authentication. For example, when performing login authentication with SPA, you may want to log in using Firebase with a client (JavaScript), send the acquired token to the server, and link the uid in JWT with the user managed by the application. I think there is.

スクリーンショット 2019-02-19 14.53.20.png

This time, I will mainly write the verification method of JWT on the server side (java + SpringBoot).

Client implementation

This time I will use Nuxt. How to get and send tokens.

Authentication process (excerpt)


async signin() {
  try {
    //Authenticate using email address and password
    await firebase.auth().signInWithEmailAndPassword(this.email, this.password)

    //Get ID token (JWT)
    const token = await firebase.auth().currentUser.getIdToken(true)

    //Save to local storage
    localStorage.setItem('token', token)

    //Transition to the page after authentication
    this.$router.push('/')
  } catch (e) {
    //At the time of authentication error, token acquisition error
    console.log(e)
  }
}

~/plugins/axios.js


export default function ({ $axios }) {
  $axios.onRequest(config => {
    const token = localStorage.getItem('token');
    if (token) {
      //If there is a token in the local storage, give it to the request header (Authorization)
      config.headers.common['Authorization'] = "Bearer " + token;
    }
  })
}

Server-side implementation

Use Spring Security's OnecePerRequestFilter.

In addition, the following libraries are used.

Library name Use
OkHttp HTTP client
jjwt JWT verification

LoginFilter.java


@Component
public class LoginFilter extends OncePerRequestFilter {

  //Register ObjectMapper as a bean in advance.
  @Autowired
  ObjectMapper objectMapper;

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {

    //Set login user information in context
    SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken(
        auth(request), null));

    filterChain.doFilter(request, response);
  }

  //Get login user information
  private LoginUser auth(HttpServletRequest request) {
    
    //Extract JWT from request header
    String token = getToken(request);

    try {

      //Verify JWT, get complaints
      //If the validation fails, an exception is thrown.
      Jws<Claims> claim = Jwts.parser()
          .setSigningKeyResolver(new GoogleSigningKeyResolver()) //Need your own resolver
          .parseClaimsJws(token);

      //Get the uid from the body part of the claim
      String uid = (String)claim.getBody().get("user_id");

      //Get uid and search user information
      User user = userService.findByUid(uid);

      if(user == null){
        throw new BadCredentialsException("User does not exist");
      }

      return new LoginUser(user);

    } catch (Exception e) {
      throw new BadCredentialsException("The token is invalid");
    }
  }

  //Get the token from the request header.
  private String getToken(HttpServletRequest request) {
    String token = request.getHeader("Authorization");
    if (token == null || !token.startsWith("Bearer ")) {
      return null;
    }
    return token.substring("Bearer ".length());
  }

  /**
   *We will return the public key used for signing.
   * 
   * @author h.ono
   *
   */
  public class GoogleSigningKeyResolver extends SigningKeyResolverAdapter {

    @Override
    public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {

      try {
        Map<String, Object> map = getJwks();

        if (map.isEmpty()) {
          return null;
        }

        String keyValue = (String) map.get(jwsHeader.getKeyId());

        if (keyValue == null) {
          return null;
        }
        //Important point
        //Remove the start (BEGIN) and end (END) labels.
        keyValue = keyValue
            .replaceAll("-----BEGIN CERTIFICATE-----\n", "")
            .replaceAll("-----END CERTIFICATE-----\n", "");

        InputStream in = new ByteArrayInputStream(Base64.decodeBase64(keyValue.getBytes("UTF-8")));
        X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);
        return certificate.getPublicKey();

      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    /**
     *Get JWKS from Google.
     * 
     * @return JWKS
     */
    private Map<String, Object> getJwks() {
      OkHttpClient client = new OkHttpClient();
      Request request = new Request.Builder()
          .url("https://www.googleapis.com/robot/v1/metadata/x509/[email protected]")
          .build();
      try (Response response = client.newCall(request).execute()) {
        return objectMapper.readValue(response.body().string(), new TypeReference<Map<String, Object>>() {
        });
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }

}

in conclusion

I was able to associate the UID obtained from Firebase with the user who has the app. I did a lot of research, but most of the authentication was completed on the client side, so I had a hard time arriving at a method like this one. .. ..

When verifying JWT, I also tried java-jwt provided by auth0, but when getting the public key, I couldn't get it because it doesn't support X.509 format.

Reference site

Recommended Posts

Verify the ID token obtained from Firebase on the server side (Java + SpringBoot)
Kick ShellScript on the server from Java
[Java] How to retrieve the parameters passed from html on the server side
Migrate from Java to Server Side Kotlin + Spring-boot
Using the database (SQL Server 2014) from a Java program 2018/01/04
How to generate / verify ID token in Java Memo
JSON in Java and Jackson Part 1 Return JSON from the server
Apache Geode-Easy way to execute logic on the server side
How to disable Set-Cookie from API on the front side
Install the memcached plugin on MySQL and access it from Java