Spring Security OAuth 2.0 - client secret always required for authorization code grant - spring

According to the spec, requests for a token using the authorization code grant are not required to be authenticated as long as the client_id is included in the request and the client_id is the same one used to generate the code. However, with the Spring Security OAuth 2.0 implementation, it appears that basic auth is always required on the /oauth/token endpoint even if the client was never assigned a secret.
It looks like there is support for allowing clients without a secret due to the isSecretRequired() method in the ClientDetails interface. What do I need to do to enable clients without a secret to be authenticated at the /oauth/token URL?
4.1.3. Access Token Request
The client makes a request to the token endpoint by sending the
following parameters using the "application/x-www-form-urlencoded"
format per Appendix B with a character encoding of UTF-8 in the HTTP
request entity-body:
grant_type
REQUIRED. Value MUST be set to "authorization_code".
code
REQUIRED. The authorization code received from the
authorization server.
redirect_uri
REQUIRED, if the "redirect_uri" parameter was included in the
authorization request as described in Section 4.1.1, and their
values MUST be identical.
client_id
REQUIRED, if the client is not authenticating with the
authorization server as described in Section 3.2.1.
If the client type is confidential or the client was issued client
credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.

Authenticating the client using the form parameters instead of basic auth is enabled using the allowFormAuthenticationForClients() method as shown in the code sample below.
class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
#Override
void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
}
}
The allowFormAuthenticationForClients() method triggers the addition of the ClientCredentialsTokenEndpointFilter which allows for authentication via form parameters.

Spring allows you to define OAuth2 clients with an empty secret.
These can be considered "public" clients, or clients that are unable to keep a secret. Think of Javascript apps, mobile apps, ..... You typically don't want to have client secrets stored there.
As you point out, according to the OAuth2 spec, a token endpoint can opt to not require a secret for these public clients.
So in Spring, simply define an OAuth2 client with an empty secret, and configure it for a limited set of grant types (authorization_code and refresh_token)
The Spring Security implementation token url will accept a token exchange without a client secret for that particular OAuth2 client.

In order to solve the issue see the method loadUserByUsername of class: org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService:
if (clientSecret == null || clientSecret.trim().length() == 0) {
clientSecret = this.emptyPassword;
}
Probably in your case emptyPassword has not been initialized with empty encoded password by your password encoder.
Set missing password encoder in AuthorizationServerConfigurerAdapter:
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder);
}

This worked for me
#Override
public void configure(AuthorizationServerSecurityConfigurer cfg) throws Exception {
cfg
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
.passwordEncoder(clientPasswordEncoder());
}
#Bean("clientPasswordEncoder")
PasswordEncoder clientPasswordEncoder() {
return new BCryptPasswordEncoder(4);
}
Test 1:
Test 2:

Initially I had a similar setup to the accepted answer, which is definitely a prerequisite to make this work. But what is missing is that you cannot simply set the password to null. You must set it to an empty password, for example like this:
String secret = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("");
clientDetails.setClientSecret(secret);
If you don't do this, you will still get a 401!

I use Spring boot 2.5.0 and I do not need the allowFormAuthenticationForClients(). My PasswordEncoder looks like this:
#Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return BCrypt.hashpw(charSequence.toString(), BCrypt.gensalt());
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return BCrypt.checkpw(charSequence.toString(), s);
}
};
}
I generated the secret as empty string through this website
just click on the button "Bcrypt" and take the hash which you will insert into a database.

like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client").secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("")) //empty
.authorizedGrantTypes("authorization_code", "refresh_token")
.redirectUris("http://www.dev.com")
.scopes("all")
.autoApprove(true);
} #Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
It is ok.

Related

Spring oauth2login oidc grant access based on user info

I'm trying to set up Authentication based on this tutorial: https://www.baeldung.com/spring-security-openid-connect part 7 specifically.
I have filled properties and configured filter chain like this:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin.permitAll());
return http.build();
}
which works, but now all users from oidc can connect log in. I want to restrict access based on userinfo. E.g. add some logic like:
if(principal.getName() == "admin") {
//allow authentication
}
are there any way to do it?
I tried to create customer provider like suggested here: Add Custom AuthenticationProvider to Spring Boot + oauth +oidc
but it fails with exception and says that principal is null.
You can retrieve user info when authentication is successful and do further checks based user info.
Here is sample code that clears security context and redirects the request:
#Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if(authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
// OidcUser or OAuth2User
// OidcUser user = (OidcUser) token.getPrincipal();
OAuth2User user = token.getPrincipal();
if(!user.getName().equals("admin")) {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
redirectStrategy.sendRedirect(request, response, "login or error page url");
}
}
}
}
Are you sure that what you want to secure does not include #RestController or #Controller with #ResponseBody? If so, the client configuration you are referring to is not adapted: you need to setup resource-server configuration for this endpoints.
I wrote a tutorial to write apps with two filter-chains: one for resource-server and an other one for client endpoints.
The complete set of tutorials the one linked above belongs to explains how to achieve advanced access-control on resource-server. Thanks to the userAuthoritiesMapper configured in resource-server_with_ui, you can write the same security expressions based on roles on client controller methods as I do on resource-server ones.

How can I implement access tokens for a public Spring Boot API?

I have a public facing API created with Spring Boot. I want users of the API to include a token with requests so that I can ensure they are allowed to access it and also track how many requests are made within a certain period of time.
I'm a little confused on how to approach this because everything I am reading online is where a user sends their user/password and the application returns a token. This is not what I'm looking for.
I want something similar to when you use Google APIs or similar APIs where your token never changes unless you want it to change and you do not send Google API your user/pass on the first request.
Maybe I'm not using the correct terminology. Can someone point me in the right direction?
If you want to authenticate using a static token for each user you will have to create a custom AuthenticationManager that gets your request header and tries to match it aginst known keys.
Example using single key, you'd have to add a lookup to user table if needed
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
http.authorizeRequests(auth -> auth
.anyRequest().authenticated());
http.addFilter(tokenFilter());
http.csrf().disable();
return http.build();
}
public Filter tokenFilter()
{
AuthenticationTokenFilter filter = new AuthenticationTokenFilter(authenticationTokenHeader);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
protected AuthenticationManager authenticationManager()
{
final AuthenticationManager authenticationManager = new AuthenticationManager()
{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
String principal = (String) authentication.getPrincipal();
if (!authenticationTokenValue.equals(principal))
{
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
}
};
return authenticationManager;
}
Do keep in mind that this approach is not the most secure, and if you'r application is public facing I would not recommend using this. And would recommend either using Authorization header with username/password or JWT
As a side note, I think you'r mistaken on how the google API authenticates. To the best of my knowledge all google APIs use Oauth2 for authentication, the static key/file you have does not provide access to the API it is only good for retrieving a short lived access token, in essence this would be not much different from standard JWT where you use some form of credentials to get a token with which you access the API.
for more info on JWT authentication:
https://www.baeldung.com/spring-security-oauth-jwt
https://blog.softtek.com/en/token-based-api-authentication-with-spring-and-jwt

Can I use both introspection server and local check for authorize token? Spring Boot - Security

I want to
introspect JWT token on remote server
and then check locally if scope/aud/iss/exp are correct
How can this be done most easily in Spring Boot?
As I understand first case is something similar to opauqeToken functionality (but I have normal JWT) and second case is more like using jwt
Spring Security only supports JWTs or Opaque Tokens, not both at the same time.
If I use opaqueToken, then validation on remote server is done without any effort (even if that's JWT)
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/api/**").hasAuthority("SCOPE_" + scope)
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri(this.introspectionUri)
.introspectionClientCredentials(this.clientId, this.clientSecret)
));
return http.build();
I have scope verified. Now I want to check iss, aud, exp. Is that doable with opaqueToken?
Or should I use jwt auth instead?
IMHO opaqueToken can be JWT, so now the question is how to verify and inspect it locally after remote introspection?
It's kind of hybrid of two different approaches, but hopefully you know the simple way how to do it.
Ok, I think I have my answer. I created my own introspector which is implementing OpaqueTokenIntrospector
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector(
"introspect-url",
"client-id",
"client-secret"
);
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal introspected = this.delegate.introspect(token);
// SOME LOGIC
}
}
and I added it as a #Bean
#Bean
public OpaqueTokenIntrospector tokenIntrospector() {
return new JwtOpaqueTokenIntrospector();
}

Spring-security - httponlycookie into existing jwt intergration?

I have been told it is insecure to just use JWT without HttpOnly cookie when using a seperate frontend-service.
As suggested here:
http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/
HttpOnly Cookie: https://www.ictshore.com/ict-basics/httponly-cookie/
I currently have a working JWT system so i'm trying to upgrade this to support the cookie implementation.
I firstly changed my SecurityConfiguration to the following:
private final UserDetailsService uds;
private final PasswordEncoder bcpe;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().logout().deleteCookies(CustomAuthorizationFilter.COOKIE_NAME)
.and().authorizeRequests().antMatchers("/login/**", "/User/refreshToken", "/User/add").permitAll()
.and().authorizeRequests().antMatchers(GET, "/**").hasAnyAuthority("STUDENT")
.anyRequest().authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO FUCKING IDEA WHAT THIS DOES
return super.authenticationManagerBean();
}
From here I am trying to insert the actual cookie implementation into my CustomAuthorizationFilter:
public class CustomAuthorizationFilter extends OncePerRequestFilter { // INTERCEPTS EVERY REQUEST
public static final String COOKIE_NAME = "auth_by_cookie";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/login") || request.getServletPath().equals("/User/refreshToken/**")){ // DO NOTHING IF LOGGING IN OR REFRESHING TOKEN
filterChain.doFilter(request,response);
}
else{
String authorizationHeader = request.getHeader(AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
try {
String token = authorizationHeader.substring("Bearer ".length());
//NEEDS SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build(); // USING AUTH0
DecodedJWT decodedJWT = verifier.verify(token);
String email = decodedJWT.getSubject(); // GETS EMAIL
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); });
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
catch (Exception e){
response.setHeader("error" , e.getMessage() );
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
else{ filterChain.doFilter(request, response); }
}
}
}
What I don't know is where to insert the cookie reading & where to wrap it. Does it wrap around the JWT?
I did see this implementation:
public class CookieAuthenticationFilter extends OncePerRequestFilter {
public static final String COOKIE_NAME = "auth_by_cookie";
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
Optional<Cookie> cookieAuth = Stream.of(Optional.ofNullable(httpServletRequest.getCookies()).orElse(new Cookie[0]))
.filter(cookie -> COOKIE_NAME.equals(cookie.getName()))
.findFirst();
if (cookieAuth.isPresent()) {
SecurityContextHolder.getContext().setAuthentication(
new PreAuthenticatedAuthenticationToken(cookieAuth.get().getValue(), null));
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
Though this mentions its the "authenticationFilter", I do have an authentication filter though it is less comparable to this CookieAuthenticationFilter than CustomAuthorizationFilter:
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authManager;
public CustomAuthenticationFilter authManagerFilter;
private UserService userService;
#Override // THIS OVERRIDES THE DEFAULT SPRING SECURITY IMPLEMENTATION
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, password);
return authManager.authenticate(authToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
// SPRING SECURITY BUILT IN USER
User springUserDetails = (User) authentication.getPrincipal();
// NEEDS SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); // THIS IS USING AUTH0 DEPENDENCY
String access_token = JWT.create()
.withSubject(springUserDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 120 * 60 * 1000)) // this should be 2 hours
.withIssuer(request.getRequestURI().toString())
.withClaim("roles", springUserDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(springUserDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 120 * 60 * 1000)) // this should be 2 hours
.withIssuer(request.getRequestURI().toString())
.withClaim("roles", springUserDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
...
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
}
Any suggestions are welcome!
By looking at all your custom code I would strongly recommend that you actually read the spring security documentation of the different authentication types that are available and look up the advantages and disadvantages.
And understand that there are security standards for how logins should be built and what you have built is insecure, non-scalable custom made which is very bad practice.
but here is a short recap:
FormLogin
The user authenticates themselves presenting a username and a password. In return, they will get a session cookie that contains a random string but is mapped to a key-value store on the server side.
The cookie is set to httpOnly and httpSecure which means it's harder to steal them and it's not vulnerable to XSS in the browser.
I just want to emphasize the cookie contains a random string, so if you want user information you either return the cookie after login and userinfo in the body or you do an additional call to a user endpoint and fetch user information.
The downside is that this solution does not scale if you want 5 backend servers you need something like Spring Session and set up a store, that stores the session so that it is shared between the backend servers.
Upside, we can just server-side invalidate the cookie whenever we want. We have full control.
oauth2
Well, this is the one most people know about, you want to login, and you are redirected, to an issuer (a different server). You authenticate with that server, the server gives you a temporary token that you can exchange for an opague token.
What is in opague token, well it's just a random text string that the issuer keeps track of.
Now when you want to call your backend you setup your backend as a resource server, that you present the token for in a header. The resource server extracts the token from the header, asks the issuer if the token is valid, and it answers yes or no.
Here you can revoke tokens, by going to the issuer and saying "this token is not valid anymore" and next time the token is presented it will check with the issuer that it is blocked and we are fine.
oauth2 + JWT
Like above but instead of having an opague token, we instead send a JWT to the client. So that, when the JWT has presented the resource server, does not have to ask the issuer if the token is valid. We can instead check the signature using a JWK. With this approach, we have one less call to the issuer to check the validity of the token.
JWT is just a format of a token. Opague token = random string, JWT = signed data in the format of JSON and used as a token.
JWTs were never meant to replace cookies, people just started using them instead of cookies.
But what we loose is the ability to know to revoke tokens. As we don't keep track of the JWTs in the issuer and we don't ask the issuer on each call.
We can reduce the risk here by having tokens that are short-lived. Maybe 5 minutes. But remember ITS STILL A RISK, for 5 mins malicious actors can do damage.
Your solution
If we look at your custom solution, which many people on the internet are building which has many many flaws is that you have built a FormLogin solution that gives out JWTs and hence comes with all the problems of JWTs.
So your token can be stolen in the browser as it does not have the security that comes with cookies. We have no ability to revoke tokens if it gets stolen. It is not scalable and it is custom written which means one bug and the entire application's data is compromised.
So basically all the bad things from the solutions above are combined here into one super bad solution.
My suggestion
You remove all your custom code and look at what type of application you have.
If it is a single server small application, use FormLogin, don't use JWTs at all. Cookies have worked for 20 years and they are still fine. Don't use JWTs just because want to use JWTs.
If you are writing a larger application, use a dedicated authorization server like okta, curity, spring authorization server, keycloak.
Then setup your servers to be resource servers using the built-in resource server functionality that comes with spring security and is documented in the JWT chapter in the docs.
JWTs were from the beginning never meant to be exposed to clients, since you can read everything in them they were meant to be used between servers to minimize calls to issuers because the data is signed so each server could check the signature by themselves.
Then the entire javascript community and lazy developers started writing custom insecure solutions to give out JWTs to the client.
Now everyone just googles a tutorial of spring security with JWT and builds something custom and insecure and then ask on stack overflow when "someone has pointed out that their solution is insecure".
If you are serious about building a secure login read the following:
Spring security official documentation, chapters formlogin, oauth2, JWT
The oauth2 specification
The JWT specification
Curity has good documentation about oauth2 https://curity.io/resources/learn/code-flow/
FormLogin spring
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
oauth2 spring
https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html
configure your application to handle JWTs
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
Some pointers about your code
this is completely unneeded. You have overridden a function and then you are calling the default implementation, also think about your languages in your comments.
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO FUCKING IDEA WHAT THIS DOES
return super.authenticationManagerBean();
}
Also, this entire class can be removed
public class CustomAuthorizationFilter extends OncePerRequestFilter
If you want to handle JWTs and make your server a resource server all you do is as the documentation states https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-sansboot
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
// This line sets up your server to use the built in filter
// and accept JWT tokens in headers
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
you can then setup a JWTDecoder using the built-in Nimbuslibrary that comes with spring security
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
Since it's a Bean it will automatically get injected, so we don't have to manually set anything.
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Here you have stated that you want the server to be stateless which means you have disabled cookies as cookies are what the server uses to retain the state from the clients. And then you are trying to implement a custom cookie filter.
Once again, you have to decide, are you going to use FormLogin with cookies, or oauth2 + JWT because now you are doing a mish-mash between.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
Is most likely not needed as I assume that both uds and bcpe are beans, components, etc., and will automatically get injected. No need to make something a bean THEN manually set it. You make something a bean so you DON'T have to set it manually. But you are doing both.

Is it possible to get an access_token from Spring OAuth2 server without client secret?

I am using Spring Security's OAuth2 server implementation. I am trying to get the access_token from the servers' /oauth/token endpoint using the OAuth2 "Password" grant type by only supplying username and password and the client id without the client secret.
This works fine as long as I provide the client id and the client secret in the Authorization header of my HTTP request like so:
curl -u clientid:clientsecret http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"
Following the advice here: Spring OAuth2 disable HTTP Basic Auth for TokenEndpoint, I managed to disable HTTP Basic authentication for the /auth/token endpoint. But when I tried to get the access_token via cURL like so:
curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"
I got a BadCredentialsException and could see the message:
Authentication failed: password does not match stored value
in my servers' log. At this point I was slightly irritated, because it was my understanding that this message only shows up when there's something wrong with the username and/or password, not the client id and/or secret. After additionally supplying the client secret in the cURL command like so:
curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT&client_secret=SECRET"
everything was fine again.
So does that mean I have to supply the client secret one way or another to access the /auth/token endpoint?
PS: I am aware of the fact that regarding security it is generally a good idea to protect this endpoint via HTTP Basic authentication, but there are some use cases where one would rather be able to do without.
Edit:
I seem to have found a way to omit the client secret. Here's my OAuth2 server configuration (notice the calls to allowFormAuthenticationForClients() and autoApprove(true)):
#Configuration
#EnableAuthorizationServer
class OAuth2Config extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
public OAuth2Config(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauth) throws Exception {
// allows access of /auth/token endpoint without HTTP Basic authentication
oauth.allowFormAuthenticationForClients();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("acme")
.autoApprove(true) // <- allows for client id only
.authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("openid");
}
}
Edit II:
The question here: Spring Security OAuth 2.0 - client secret always required for authorization code grant is very closely related to this one but deals with the OAuth2 grant type "Authorization Code", which results in a different workflow like the one you get with grant type "Password".
According to the specification (RFC 6749), if the client type of your application is public, a client secret is not required. On the contrary, if the client type is confidential, a client secret is required.
If Spring offers an API to set the client type, try to set the client type to public.
Spring Boot's implementation requires that a client-secret be passed in to authenticate. You can however override this by creating a bean of type AuthorizationServerConfigurer and configuring it yourself. This is the link to the documenation...
Use basic auth but leave the password empty.
In the implementation of AuthorizationServerConfigurerAdapter override configure and set password encoder to raw text encoder (do not use it as a default password encoder!).
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(plainTextPasswordEncoder())
.allowFormAuthenticationForClients();
}
private PasswordEncoder plainTextPasswordEncoder() {
return new PasswordEncoder() {
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return !StringUtils.hasText(encodedPassword) || passwordEncoder.matches(rawPassword, encodedPassword);
}
#Override
public String encode(CharSequence rawPassword) {
return passwordEncoder.encode(rawPassword);
}
};
}
}
Now, for OAuth client details (in memory or in a database), set the client secret to null. In this case, the client will be treated as public and will not require client_secret parameter. If you set client secret for OAuth client details (e.g. BCrypt hash), then the client will be treated as confidential. It will rely on default password encoder (e.g. BCrypt) and require client_secret parameter to be sent in order to obtain an access token.

Resources