Project Trouble Shooting - Email Verification API : POST vs GET With JPA
Table of contents
- #Post vs GET (Email Verification API)
- #Foreword
- #Problem : Verify Email API , 401 error _ With POST @RequestBody
- # My First Approach : 401 only comes from Misconfiguration of Spring Security
- # My Second Approach : Code is saved well in DB,,,, If so, why.............?
- #My Third Approach : Ah? maybe ambiguous mapping?
- #Solution : GET Instead of POST
- #Review + Approach for new API
- #etc
#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
Send Email Verification Code
(A. Send Verification Code API)
Save verification code with a specific email that is used as id.
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)
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;
}
}
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
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.
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}?email=test@naver.com
Query Params
Param name | Param Value | PathVariable |
issued 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: cheng286@naver.com 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.