OAuth 2.0
- Anand Nerurkar
- May 11, 2024
- 3 min read
IDP - Identity Provider which maintain user credential or user has registered on that platform.
For Ex: Google, Facebook,Linkedin
OIDC- Open ID Connect provide authentication with the help of IDP.
Authorization Code Flow
==
once authenticated, consent is being asked
create auth server
create custom user details service
@Service
@Transactional
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(11);
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if(user == null) {
throw new UsernameNotFoundException("No User Found");
}
return new org.springframework.security.core.userdetails.User(
user.getEmail(),
user.getPassword(),
user.isEnabled(),
true,
true,
true,
getAuthorities(List.of(user.getRole()))
);
}
private Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<>();
for(String role: roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
add configuration for auth server
===
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("api-client")
.clientSecret(passwordEncoder.encode("secret"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/api-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("api.read")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:9000")
.build();
}
}
add defaultsecurityconfig
==
@EnableWebSecurity
public class DefaultSecurityConfig {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Autowired
public void bindAuthenticationProvider(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder
.authenticationProvider(customAuthenticationProvider);
}
}
add custom authetication provider
===
@Service
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails user= customUserDetailsService.loadUserByUsername(username);
return checkPassword(user,password);
}
private Authentication checkPassword(UserDetails user, String rawPassword) {
if(passwordEncoder.matches(rawPassword, user.getPassword())) {
return new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword(),
user.getAuthorities());
}
else {
throw new BadCredentialsException("Bad Credentials");
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
add websecurityconfig in client side project
==
Authorization code flow: This flow allows a client application to access protected resources like web APIs. It’s designed for confidential applications, such as regular web applications. The app’s client secret must be securely stored on the client side. This flow is suitable for server-side web applications where the source code is not exposed publicly.
Client credentials flow: This is an OAuth 2.0 grant type where applications request access tokens directly without user intervention. Used when the client is also the resource owner, it authenticates the application using its client ID and secret. Suitable for server-to-server authentication, the returned access token grants access to specific, predefined scopes.
Resource owner password credentials: This is an OAuth 2.0 grant type in which the resource owner (typically the user) provides their service credentials (username and password) directly to the client application. The client then exchanges these credentials for an access token from the authorization server.
Hybrid flow: Suitable for applications that can store client secrets, this flow enables immediate access to ID tokens alongside ongoing access to refresh tokens.
Device authorization flow: This flow is suitable for input-constrained devices that connect to the internet. Instead of authenticating the user directly, the device asks the user to go to a link on their computer or smartphone and authorize the device.
PKCE flow: An extension to the OAuth 2.0 Authorization Code flow, it is designed to enhance its security when used with public clients, like mobile applications or single-page web apps. These clients can’t reliably store client secrets.
The main difference between the authorization code flow and the client credentials flow is that the client credentials flow relies on application authorization rather than involving the user.
Comments