I want to implement authorization via Google and Microsoft in my full stack app (Angular 6 for front-end, Spring boot for back-end) and I've got a problem.
I follow this scheme which describes how the flow should be implemented:
OAuth2 Authorization Flow:
The problem is with Step 4 where front-end should send authorization code to back-end. I just don't know which endpoint to use and how to configure it in Spring Security configuration.
This endpoint should get authorization code, check it on OAuth provider side, request access and refresh token and remember the authorized user.
Maybe there is an example of how it should be done? Google answers do not seem appropriate.
What I already did:
Added #EnableOAuth2Sso annotation to the security configuration. But this is not what I actually need, because it enables authorization on back-end side. (redirecting to Google page works from back-end, but I need from front-end)
#EnableOAuth2Sso
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}
Tried configuring Oauth2Login by myself.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository)
.authorizedClientService(new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository));
}
Gradle dependencies:
<pre>
compile 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.0.RELEASE'
compile 'org.springframework.security:spring-security-oauth2-jose'
compile 'org.springframework.security:spring-security-oauth2-client'
</pre>
What I expect:
Some endpoint on back-end side, which accepts authorization code. For example:
URL: localhost:8080/oauth2/authcode/google
<pre>
{
"authorization_code": "...."
}
</pre>
And returns access token:
<pre>
{
"access_token": "...",
"expires": "..."
}
</pre>
And what is happening in the middle: back-end exchanges authorization code to access token and refresh token and saves it to own DB.
Thank you!
Related
I am having multiple downstream services to which I am routing via a spring cloud gateway service which also has okta auth. Currently I am able to route authenticate users at the gateway and return 401 for all requests without a valid bearer token. However when I try to implement role based auth, i.e. GET requests can be sent downstream for everyone group, but POST requests require 'admin' group membership in okta. This does not work as any authenticated user is currently able to make post requests. I have added claims to the access/id tokens and checked them in the Token preview section of my default Authorisation server in okta. Following is my security configuration.
#EnableWebSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers(HttpMethod.POST,"/*").hasAuthority("admin")
.anyRequest().authenticated())
.oauth2ResourceServer().jwt();
}
}
Due to only the gateway having okta auth and downstream services being protected by api token, I cannot implement preAuthorize and have to rely on httpsecurity, but I seem to be missing something
I'm a bit confused regarding whether I should be accessing my Spring Boot Resource Server via an access_token or an id_token.
First, let me quickly explain my setup:
Spring Boot app as an OAuth 2.0 Resource Server. This is configured as described in the Spring docs: Minimal Configuration for JWTs This app provides secured #Controllers that will provide data for a JavaScript SPA (eg. React)
Google's OAuth 2.0 AP / OpenID Connect already configured (Credentials, Client Id, Client Secret)
A JavaScript SPA app (eg. React) that logs the user into Google and makes requests to the Spring Boot Resource Server for secured data. These requests include the Authorization header (with Bearer token obtained from Google) for the logged in user.
For development purposes, I'm also using Postman to make requests to the Spring Boot Resource Server
I can easily configure Postman to get a token from Google. This token response from Google includes values for access_token, id_token, scope, expries_in and token_type.
However, my requests to the Resource Server are denied when Postman tries to use the value from retrieved token's access_token field as the Bearer in the Authorization header
The only way I'm able to successfully access the secured #Controllers is by using the id_token as the Bearer in the Authorization header.
Is it expected that I should use the id_token as the Bearer in the Authorization header? Or is it expected that I should use the access_token?
Some additional relevant info:
The value of the id_token is a JWT token. The value of the access_token is not a JWT token. I know this because I can decode the id_token on jwt.io but it is unable to decode the value of the access_token. Further, the Spring Boot Resource Server fails with the following when I send the access_token as the Bearer in the Authorization header:
An error occurred while attempting to decode the Jwt: Invalid unsecured/JWS/JWE header: Invalid JSON: Unexpected token ɭ� at position 2.
This blog post Understanding identity tokens says the following:
You should not use an identity token to authorize access to an API.
To access an API, you should be using OAuth’s access tokens, which are intended only for the protected resource (API) and come with scoping built-in.
Looking at at the spring-security-samples for using OAuth2 Resource Server, I see the value of there hard-coded access_token (for testing purposes) is indeed a valid JWT. As opposed to the access_token returned from Google which is not a JWT.
In summary:
I can access my Spring Boot Resource Server using the value of the id_token obtained from Google. The value of the access_token is not a JWT and fails to parse by Spring Boot.
Is there something wrong with my understanding, my configuration or what? Does Google's OpenId Connect behave differently regarding how the access_token works?
Happy to clarify or add more info if needed. Thanks for your consideration and your patience!
The blog post you mentioned is correct in my view, and I believe the OpenID Connect 1.0 spec does not intend for an id_token to be used for access purposes.
Like you, I expected that using Google as an Authorization Server would work out of the box, because Spring Security works with Google as a common OAuth2 provider for providing social login. However, this is not the case, and I believe it is not really intended, because Google is not really your authorization server. For example, I don't believe you can configure Google to work with scopes/permissions/authorities of your domain-specific application. This is different from something like Okta, where there are many options for configuring things in your own tenant.
I would actually recommend checking out Spring Authorization Server, and configuring Google as a federated identity provider. I'm working on a sample for this currently and it will be published within the next week or so (see this branch).
Having said that, if you're still interested in a simple use case where Google access tokens are used for authenticating with your resource server, you would need to provide your own opaque token introspector that uses Google's tokeninfo endpoint. It doesn't match what Spring Security expects, so it's a bit involved.
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests((authorizeRequests) -> authorizeRequests
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
// #formatter:on
return http.build();
}
#Bean
public OpaqueTokenIntrospector introspector() {
return new GoogleTokenIntrospector("https://oauth2.googleapis.com/tokeninfo");
}
}
public final class GoogleTokenIntrospector implements OpaqueTokenIntrospector {
private final RestTemplate restTemplate = new RestTemplate();
private final String introspectionUri;
public GoogleTokenIntrospector(String introspectionUri) {
this.introspectionUri = introspectionUri;
}
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
RequestEntity<?> requestEntity = buildRequest(token);
try {
ResponseEntity<Map<String, Object>> responseEntity = this.restTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {});
// TODO: Create and return OAuth2IntrospectionAuthenticatedPrincipal based on response...
} catch (Exception ex) {
throw new BadOpaqueTokenException(ex.getMessage(), ex);
}
}
private RequestEntity<?> buildRequest(String token) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("access_token", token);
return new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(introspectionUri));
}
}
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://accounts.google.com
jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
I am using Spring Cloud Gateway as a API Gateway for our system. We would like to delegate all authentication (oauth) to that component. I was looking at the source code of Spring Oauth2 Client but I don't see any place where I can "plug in" to do what I need.
I would like to catch the moment, when the code exchange is successful and make a redirect with id_token and refresh_token in cookie or query param. We don't store any session as well - whole authentication is meant to stateless.
I am configuring SecurityWebFilterChain (security for WebFlux) like this:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(Customizer.withDefaults())
.oauth2Login();
http.csrf().disable();
return http.build();
}
I tried to use successHandler
.oauth2Login(c -> c.authenticationSuccessHandler(successHandler));, but in that moment I don't access to refresh_token (have only WebFilterExchange, Authentication in arguments) and I am not even sure how should I perform the redirect form that place.
Is there any way to achieve this?
I have an API configured with Spring Security's permitAll():
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api").permitAll()
.and().httpBasic();
}
}
While this works, I noticed if you include an Authorization Basic header, Spring Security will still try to process and validate the auth header.
I'm using AWS API gateway for authorization. AWS will then forward the request to my application. The thing is, AWS API gateway forwards the auth header to the app.
What's the best way to solve this, do I remove the header before AWS sends the request to the app? or I configure Spring Security to disregard the auth header if an API is public?
I am trying to define and secure a RESTful API using Spring Boot. Ideally, I would like to use Spring Social and allow clients (web and mobile) to login via Facebook.
What is working
So far, I managed to have a working API using #RestController and secure it with a basic Spring Security configuration as follows:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/**").authenticated()
.antMatchers(HttpMethod.PUT, "/api/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/**").authenticated()
.anyRequest().permitAll()
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
The antMatchers could probably be improved, but I made it like this for my own clarity for now and it works fine. Doing GET requests is allowed and all others required to send the standard user:password given by Spring Security at runtime. An example using httpie:
http POST user:a70fd629-1e29-475d-aa47-6861feb6900f#localhost:8080/api/ideas/ title="My first idea"
Which right credentials, it sends a 200 OK back, otherwise a 401 Unauthorized.
Spring Social
Now, I am stuck and can't get my head around using Spring-Social-Facebook to get working with my current setup and keep fully RESTful controllers. Using standard forms and redirects seems trivial, but I couldn't find any solution for a REST-based approach that easily supports web and mobile clients for example.
As I understand, the client will have to handle the flow, since the back-end won't send any redirects to the /connect/facebook URL.
I followed the tutorial Accessing Facebook Data and it works on its own. However, I would like to avoid having to have those facebookConnect.html and facebookConnected.html templates like in the tutorial. So I don't know how to have change that.
Another Spring Boot tutorial for OAuth is also nice and working, but I would like to stick with Spring Social if possible due to the simplicity.
This post, helped for the Method not allowed issue of the /connect/facebook redirect when using those views mentioned above.
Post about Social Config. Probably, I am missing something there.
Any advice, solution or link to a better tutorial would be really helpful.
Thanks!
UPDATE 1
Now, I have a working website with traditional User SignUp and Login over forms. I have a "Login with Facebook" button that sends me over the "OAuth dance". So next issue is that I have to create somehow the User manually after the Facebook login has been successful, because for the moment both "logins" are not related, so even though the user is logged in with Facebook, he doesn't yet have an associated User object with the right authorisations.
SocialAuthenticationFilter by default, redirects to '/signup' in the case you described, user is signed in from a social app, however, no local account exists. You can provide a handler to create a local account. This is also covered in the spring-socal samples.
#RequestMapping(value = { "/signup" }, method = RequestMethod.GET)
public String newRegistrationSocial(WebRequest request, Model model) throws Exception {
String view = "redirect:/home";
try {
Connection<?> connection = providerSignInUtils.getConnectionFromSession(request);
if (connection != null) {
UserProfile up = connection.fetchUserProfile();
registerUser(up.getFirstName(), up.getLastName(), up.getEmail(), "DummyPassword");
providerSignInUtils.doPostSignUp(up.getEmail(), request);
//SignInUtils.signin(up.getEmail());
...
...
}
}
return view;
}