I am working on an API service that is meant to do the following:
- Allow users to sign in via Google.
- Create the user in the database based on the information retrieved.
- Provide the user with a JWT token to be used for authentication so that requests are uniquely identified with said user.
- Allow the user to be able to use the obtained token to perform API requests against my service.
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
I am unsure how can I go about this and what exactly do I need. So far I have the following
Main Application class:
@SpringBootApplication
@EnableWebSecurity
@Configuration
class ApiServiceApplication {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests {
it.antMatchers("/", "/login", "/error", "/webjars/**").permitAll().anyRequest().authenticated()
}
.logout {
it.logoutSuccessUrl("/").permitAll()
}
.exceptionHandling {
it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
}
.oauth2Login { oauth2Login ->
oauth2Login.loginPage("/login")
oauth2Login.defaultSuccessUrl("/user", true)
}
.oauth2Client { oauth2Client -> }
.csrf {
it.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
}
return http.build()
}
}
fun main(args: Array<String>) {
runApplication<ApiServiceApplication>(*args)
}
User Service class for saving the user to the DB
@RestController
class UserService : OidcUserService() {
@Autowired
lateinit var userRepository: UserRepository
@Autowired
lateinit var loginRepository: LoginRepository
private val oauth2UserService = DefaultOAuth2UserService()
@GetMapping("/login")
fun authenticate(): RedirectView {
return RedirectView("/oauth2/authorization/google")
}
override fun loadUser(userRequest: OidcUserRequest?): OidcUser {
val loadedUser = oauth2UserService.loadUser(userRequest)
val username = loadedUser.attributes["email"] as String
var user = userRepository.findByUsername(username)
if (user == null) {
user = OauthUser()
user.username = username
}
loadedUser.attributes.forEach { loadedAttribute ->
val userAttribute = user.oauthAttributes.find { loadedAttribute.key == it.attributeKey && it.active }
val newAttribute = OauthAttribute(loadedAttribute.key, loadedAttribute.value?.toString())
if(userAttribute == null){
user.oauthAttributes.add(newAttribute)
}
else if(userAttribute.attributeValue != loadedAttribute.value?.toString()){
userAttribute.active = false
user.oauthAttributes.add(newAttribute)
}
}
user.oauthAuthorities = loadedUser.authorities.map { OauthAuthority(it.authority) }.toMutableList()
user.oauthToken = OauthToken(
userRequest?.accessToken?.tokenValue!!,
Date.from(userRequest.accessToken.issuedAt),
Date.from(userRequest.accessToken.expiresAt)
)
userRepository.save(user)
val login = Login(user)
loginRepository.save(login)
return user
}
}
I am not providing the data classes and corresponding repositories because what's above works fine - upon accessing the /login endpoint, the user is redirected to Google where after authentication the user is saved in the database along with the corresponding information.
My main issue is that I am not really sure how to go about authenticating each request. I've tried to provide an authentication Bearer in Postman that is the same as the one obtained from Google in the loadUser
method, but I'm getting back 401 unauthorized codes. When I access the server through the browser and I authenticate I can access all the endpoints just fine, but I'm guessing that it's just my session that is authenticated.