Project Trouble Shooting - Email Verification API : POST vs GET With JPA

#Post vs GET (Email Verification API)

POST

: @RequestBody (email + code)

GET

: @PathVariable(Code) @RequestParam(Email)

Url for Email Verification APIs Source Code

-> (will be updated)

#Foreword

While developing Verify Email Code, I've run into a snag. This API didn't have a proper guide line, so I had to try this from scratch. First, I tried to use hashmap for saving verification code, but I heard that using db is more practical than hashmap.

Verify Email API's sequence

  1. Send Email Verification Code (A. Send Verification Code API)

  2. Save verification code with a specific email that is used as id.

  3. When specific code is requested with its email, verify it if it's equivalent to the code in db. (B. Verify Email Verification Code API)

  4. When verified, remove that code with a specific email.

    When it's wrong, it's denied.

When client(FE) wants to request for specific code and email, I should've u sed

GET - @PathVariable (verification code) + @RequestParam (email)

#Problem : Verify Email API , 401 error _ With POST @RequestBody

401 Unauthorized -> means it's not valid in this application. (This application was already under the spring security)

What was the problem??

: I didn't fully understand POST and GET method + @RequestBody/ @PathVariable/ @RequestParam

Description

1. For verify email verification code API, I developed POST method + @RequestBody (Email + code)

2. To verify the code, I should've verified it if requested code is equivalent to saved code in db.

3.But, API that i implemented is like requesting new data rather than inquiring data from specific location.

In conclusion,

This logic can't fetch data from db successfully, so it can't verify specific data (verification code with specific email) -> It couldn't be a valid Logic for this API in the first place.

Error Snapshot

Send Email Verification Code API

: POST -> email/Send-verification-code API (Valid API)

Verify Verification Code API

: POST email/Verify-Code (Invalid API - 401 Unauthorized API)


# My First Approach : 401 only comes from Misconfiguration of Spring Security

1.Updated JWT Filter, allowing specific Url

: Add this line of code:

 if (requestURI.equals("/api/email/send-verification-code") 
    || requestURI.equals("/api/email/verify-code")) {
            chain.doFilter(request, response);
            return;
        }
javaCopy codepublic class JwtFilter extends OncePerRequestFilter {

    // Other methods and fields

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String requestURI = httpServletRequest.getRequestURI();

---------// Bypass Verification endpoints (For Email Verification API
        //, Without this, 401 Unauthorized occurs.)
        if (requestURI.equals("/api/email/send-verification-code") 
            || requestURI.equals("/api/email/verify-code")) {
            chain.doFilter(request, response);
            return;
        }
---------
        String accessToken = resolveToken(httpServletRequest);
        if (accessToken == null) {
            chain.doFilter(request, response);
            return;
        }

        // If the token is valid, it retrieves the authentication object and sets it in the SecurityContext.
        if (StringUtils.hasText(accessToken) && jwtProvider.validateToken(accessToken)) {
            Authentication authentication = jwtProvider.getAuthentication(accessToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            log.info(
                "Security Context에 'memberId: {}' 인증 정보를 저장했습니다, uri: {}",
                authentication.getName(), requestURI
            );
        } else {
            log.info("유효한 JWT 토큰이 없습니다, uri: {}", requestURI);
        }
        chain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) return null;

        // Iterates through them to find the cookie named accessToken, Returns the value of the accessToken cookie if found.
        for (Cookie cookie : cookies) {
            if (ACCESS_TOKEN_COOKIE_NAME.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
        return null;
    }
}

added logging

2.Allowed requestMatcher's specific url

Fail again

Why........? If 401 was from spring security, this API should've run well after i allow that specific url.

-> So, I could come to the conclusion, it's not related to security's configuration.



# My Second Approach : Code is saved well in DB,,,, If so, why.............?

: Hm,,, verification code is Saved in DB well. This API is saving Email and Verification code to remove them in DB when verified.

Technically Email-related API shouldn't be related to Authentication by Spring Security, This API should be allowed without Login.

Explanation

  1. Before Login Process: The email verification process typically occurs before the user is logged in. Therefore, the endpoints handling email verification (sending and verifying codes) should be accessible without requiring authentication.

  2. Removing Data from DB: To remove the verification code from the database, Spring Security needs to permit access to the endpoint that performs the deletion. This should be allowed without authentication since it is part of the verification process, which occurs before login. (Thus, I have to allow that url, but this isn't solution for the thigns that i mentioned, anyway, I made it in a wrong way : permit all Email-related API)

#My Third Approach : Ah? maybe ambiguous mapping?

Let's refine the URl -> Fail.

I tried to signup my email and then try -> Fail

Below is successful message from JwtFilter -> Fail


#Solution : GET Instead of POST

GET : @PathVariable(Code)+@RequestParam(email)

POST : @RequestBody (Requested for Code + Email)

it turns out that The 401 Unauthorized wasn't from Spring Security (My guess was all wrong.)

Whatever I tried, Failed Again With @POSTMapping...

I think I should've tried @GET With @RequestParam + @PathVariable

Basis?

I said, I need to verify verification code with specific email in DB, which means I need to retrieve specific data rather than enrolling new data.

but, POST + @RequestBody is the way of enrolling new data.

I should've fetched data from db that have to be verified.................

So, GET + @PathVariable(code) + @RequestParam(email) should've used for this.

GET /email/verify/{code} 이메일 인증

Email Verification ex): /singup/confirm/{code}?

Query Params

Param nameParam ValuePathVariable
emailEmailissued code

Request Body -> X

Response Body

{
    "code": 200       // 200 ok is enough.
}

#Review + Approach for new API

Ah!!!!!!!Finally,,,,,,,,,,,I solved this problems, it's because I didn't properly know how to use @PathVariable, @RequestParam................ I thought I need to get email and code from Request Body, it's my misconception....................

My previous logic doesn't make sense in the first place, @RequestBody should've been pair with in POST mapping.

To verify the issued verification code, We don't need to get Email and Code from "Request Body", which means it's another data that's requested, but we need to verify it, so "retrieve that data from Db and then verify it."

I missed important concepts, that's why this happeneded..........................

Tips for Implementation : If you are new to it

While Implementing member-related API + email-related API, I've learned one thing, if it's my first try, I need to focus on implementing it first without considering other factors, allowing you to focus on running applications. After I checked it runs well, I can have time to refactor it if needed like below.

When you don't know where to start, make it simple first, in doing so, you can get started, once you get started, you can finish it one by one.

oh you can consider it like a draft, code can be described as writings.

Draft(Implementation)

Success!!!!!!!!!!!!!!!!!!!!!!!!!!!! _ Finally.....................!

After checking, it runs well

Modified Version(final)


#etc

A. For this API, Hashmap can replace Database with it.

: In-memory implementation With ConcurrentHashMap

The in-memory implementation is a simplified approach to handle verification codes without persisting them to a database. Instead of saving the codes in a database, they are stored in memory using a data structure like ConcurrentHashMap. This map holds the email as the key and the corresponding verification code as the value. (but, i heard real world application uses DB for this.)

B. TransactionRequiredException

Hm,,, In real world applciation, people say they are using DB, and when it's verified, it removes from DB.

So, I will try it With VerificationCode Entity. but,

After adding VerificationCode Entity class I met error below, I thought failed again.... But! I read error message!)

Caused by: jakarta.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call

\==> add @ Transactional Annotation

C. Logging Message

2024-05-26T16:08:03.146+09:00 INFO 25848 --- [nio-8080-exec-4] c.F.S.global.security.jwt.JwtFilter : Processing request for URI: /error 2024-05-26T16:08:03.153+09:00 INFO 25848 --- [nio-8080-exec-4] c.F.S.global.security.jwt.JwtFilter : Stored authentication for memberId: for URI: /error

The log indicates that the request is being processed by the /error endpoint, which suggests that an error occurred before reaching your verifyCode method. Let's add some debugging information and ensure your security configuration allows unauthenticated access to this endpoint.