I am attempting to programmatically retrieve an OAuth Access Token back from my OAuth authorization server. I am able to retrieve a token using the following steps:
Direct browser to http://localhost:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http://localhost:10000/client/landing This endpoint requires authentication to access, so the browser is redirected to a login form, where I enter a user name & password that are validated by a custom user details service I wrote.
Browser is now on a page that verifies what scopes I want to give the client access to for my protected resources. I make my selection and hit the authorize button.
Browser is now redirected to the provided redirect uri with the authorization code as a query parameter. (Ex. http://localhost:10000/client/landing?code=J23Dxx)
I then take the code parameter and use it in a POST (in postman) to the /oauth/token endpoint. (Ex. localhost:8080/oauth/token?grant_type=authorization_code&username=clientId&password=clientSecret&code=J23Dxx&redirect_uri=http://localhost:10000/client/landing)
This returns an access token, and all is good.
The issue arises when try to access the token using a OAuth2RestTemplate from my client app. Here is my client config class:
#Configuration
#EnableOAuth2Client
public class ClientConfig {
#Value("${app.clientId}")
private String clientId;
#Value("${app.clientSecret}")
private String clientSecret;
#Value("${app.resourceId}")
private String resourceId;
#Value("${app.clientAuthUri}")
private String clientAuthUri;
#Value("${app.tokenUri}")
private String tokenUri;
#Value("${app.directUri}")
private String redirectUri;
#Bean
public OAuth2ProtectedResourceDetails getResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId(resourceId);
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setScope(Arrays.asList("read", "write"));
details.setUserAuthorizationUri(clientAuthUri);
details.setAccessTokenUri(tokenUri);
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
#Bean
public OAuth2RestOperations oAuthTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(getResourceDetails(), oAuth2ClientContext);
return template;
}
}
Then I attempt to get an access token using the OAuth rest template:
#Service
public class OAuthService {
#Autowired
private OAuth2RestOperations oauthTemplate;
public OAuth2AccessToken getToken() {
return oauthTemplate.getAccessToken();
}
}
Running the getToken() method results in an InsufficientAuthenticationException, which makes sense, because I have not entered a username & password to access the oauth/authorize endpoint. But since I am using an authorization code flow, setting a username & password on the resource details is not an option. I have tried getting a token after going to the oauth/authorize endpoint in the same manner, but in that case I get an error about a possible CSRF attack, which I believe is due to the authorization server expecting a state parameter on the token request. Since the oauth/authorize endpoint was hit with the browser and the token endpoint hit from the OAuth2RestTemplate, no state is preserved from the authorization.
How do I utilize the OAuth2RestTemplate to access my token, or are there more fundamental problems with my design which are preventing this from working?
I am surprised that I am not finding more information about this. Am I wrong in believing that requiring authentication for the authorize & token endpoints is recommended practice? What is the standard way to utilize the OAuth2RestTemplate in a case where authentication is necessary before the resource details would be considered?
Related
I am currently using a rather simple approach to restrict a certain suburl (everything under /api/rest) and all of its subpaths via WebFluxSecurity. Some paths (everything directly under the root NOT in /api/rest) are excluded so that they can be access without authorization. However, sometimes the accessing party might send an empty authorization header which leads to unsecured endpoints returning a 401.
See the relevant code here:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Value(value = "${...}")
private String user;
#Value(value = "${...}")
private String pw;
#Bean
public MapReactiveUserDetailsService userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails user = User
.withUsername(user)
.password(encoder.encode(pw))
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/rest/**")
.authenticated()
.anyExchange()
.permitAll()
)
.httpBasic(withDefaults());
return http.build();
}
}
On stackoverflow I've only found a few suggestions how to handle this with WebSecurity. However, this is not possible for me as I use webflux security.
See e.g.
Springboot webflux throwing 401 when Authorization header sent to unrestricted endpoint
Spring Boot 2: Basic Http Auth causes unprotected endpoints to respond with 401 "Unauthorized" if Authorization header is attached
TL;DR
If you pass invalid credentials to any endpoint with httpBasic() enabled, it will return a 401 response.
One important distinction that's relevant here is the difference between authentication and authorization. The httpBasic() DSL method adds the AuthenticationWebFilter configured for HTTP Basic. The authorizeExchange(...) DSL method defines authorization rules, such as authenticated() and permitAll().
The authentication filter appears earlier in the Spring Security filter chain than the authorization filter, and so authentication happens first which we would expect. Based on your comments, it seems you are expecting authentication not to happen if you mark an endpoint as permitAll(), but this is not the case.
Whether authentication is actually attempted against a particular request depends on how the authentication filter matches the request. In the case of AuthenticationWebFilter, a ServerWebExchangeMatcher (requiresAuthenticationMatcher) determines whether authentication is required. For httpBasic(), every request requires authentication. If you pass invalid credentials to any endpoint with httpBasic() enabled, it will return a 401 response.
Additionally, a ServerAuthenticationConverter (authenticationConverter) is used to read the Authorization header and parse the credentials. This is what would fail if an invalid token (or Authorization header) is given. ServerHttpBasicAuthenticationConverter is used for httpBasic() and is fairly forgiving of invalid header values. I don't find any scenarios that fail and produce a 401 response except invalid credentials.
I have a CustomAuthenticationProvider that does a POST request to an API with username and password for authentication and the API returns an access token with expiry time.
Where do I set this token, so I can use the same token to make further calls to the API as long as the user is logged in. I also wanted to validate the token for expiry time before making another request.
Is it right approach to add the token to a customAuthenticationToken that extends UsernamePasswordAuthenticationToken and set it in the SecurityContext.
Please let me know your suggestions.
The token needs to be in the 'authorization' header for all calls. The value should be 'Bearer ' + token. If you are using a browser it gets a bit messy - let me know.
To add the authorization bearer header to all calls from Spring Boot depends on the sort of client, eg
HttpClient httpClient= new HttpClient()
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
Where the token is stored as a, probably static, variable somewhere.
In the server side you need a Filter that validates the token and marks the request as authorised - quite a bit of work - look here
Well, if you need to call another REST API, then you need to set up an http client. Since you use Spring Boot 3, WebClient is a default option, but the flow is the same for any client.
You basically store your token anywhere in memory, implement isExpired check and refresh logic.
class TokenStorage {
private String token;
void refreshToken() {
var newToken = ...;
this.token = newToken;
}
boolean isExpired() { ... }
String getToken() {
return token;
}
}
And then setup your client with custom filter so that everytime you call API, it checks whether token is expired and refreshes it if so.
I have a controller method that has an argument OAuth2AuthorizedClient with annotation #RegisteredOAuth2AuthorizedClient.
ResponseEntity<String> getFoo(
#RegisteredOAuth2AuthorizedClient("custom") OAuth2AuthorizedClient client) {
OAuth2AccessToken token = client.getAccessToken();
....
}
In order to resolve this, spring look for OAuth token on OAuth2AuthorizedClientService which actually stores the previously authenticated tokens in memory.
Now, I have a scenario where I obtain JWT token from OAuth server outside of this spring resource server, and trying to authenticate this server by passing token using Authorization header.
But when spring trying to resolve OAuth2AuthorizedClient it is looking for token in-memory with the principle of JWT (which will obviously not found since token is not obtained on this server). Hence send new login redirection for new token.
Overall question would be, is it possible to resolve OAuth2AuthorizedClient with the JWT token (obtained ourside of the server) passed in Authorization header?
I am trying to do a JUnit Tests that executes an OAuth flow.
My customer built a OAuth provider, when I make a test using postman, the postman show me a screen to fill down the credentials, after that, the postman store the information (access_token, id_token, all JWT informations), it is ok.
See the example:
My code to test is:
#Test
public void getAccessTokenViaSpringSecurityOAuthClient() {
try {
OAuth2ProtectedResourceDetails resourceDetails = googleOAuth2Details();
OAuth2RestTemplate oAuthRestTemplate = new OAuth2RestTemplate(resourceDetails);
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
OAuth2AccessToken token = oAuthRestTemplate.getAccessToken();
System.out.println(oAuthRestTemplate.getResource());
System.out.println(oAuthRestTemplate.getOAuth2ClientContext());
System.out.println(token);
Assert.assertTrue(token != null);
} catch (Exception e) {
e.printStackTrace();
}
}
public OAuth2ProtectedResourceDetails googleOAuth2Details() {
AuthorizationCodeResourceDetails googleOAuth2Details = new AuthorizationCodeResourceDetails();
googleOAuth2Details.setClientId("xxxxx");
googleOAuth2Details.setUserAuthorizationUri("https://xxx/yyy/oauth2/authorize");
googleOAuth2Details.setAccessTokenUri("https://xxx/yyy/oauth2/token");
googleOAuth2Details.setScope(Arrays.asList("openid"));
googleOAuth2Details.setPreEstablishedRedirectUri("https://www.getpostman.com/oauth2/callback");
googleOAuth2Details.setAuthenticationScheme(AuthenticationScheme.query);
googleOAuth2Details.setClientAuthenticationScheme(AuthenticationScheme.form);
return googleOAuth2Details;
}
When I run the task, this exception happens:
org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval
Is it possible to test this flow?
How can I do it?
The exception explains the problem : A redirect is required to get the users approval.
Postman is hiding the fact that once the authorization process is over, the authorization server redirects your browser to a redirect_uri, or Callback URL as Postman names it. This URL collects the authorization code delivered by the authorization server, and requests a token.
See the authorization code flow for more details :
OAuth 2.0 RFC 6749
a simplfied diagram
This means that you cannot "unit test" the authorization code grant of an authorization server. You need some kind of web server to process the callback of the authorization code flow.
You could probably test your authorization a lot faster by creating a #SpringBootTest with a Spring Boot application using #EnableOAuth2Sso. Spring Boot will auto-configure an OAuth2RestTemplate for you, and you can have it #Autowired in your JUnit test.
I'm using Spring OAuth 2.0, and I want to protect my application against URL redirect attack. Is there a way to validate the redirect URL at the authorization server ?
The authorization server should only perform a redirect if the redirect_uri matches the one registered by the client. So there shouldn't be any need for you to perform a separate check.
If in doubt, try sending an authorization request with a completely different redirect_uri and see what happens.
Should be implemented ClientDetailsService class and configure registered redirect uri's.
#Service
#Transactional
public class CustomClientDetailsService implements ClientDetailsService {
#Override
public ClientDetails loadClientByClientId(String clientId) {
String registeredRedirectUris; // Get registeredRedirectUris
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setRegisteredRedirectUri(registeredRedirectUris);
return clientDetails;
}
}