Azure Active Directory with Springboot : how to handle expired oauth token? - spring-boot

I have a springboot app which use Microsoft Azure active directory to allow authentication (oauth2).
I have followed the "how to" provided by Microsoft (https://learn.microsoft.com/en-us/java/azure/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory?view=azure-java-stable).
Everything is working well except that I have no ideas on how to handle expired token (after 1 hour) in a way that it will not affect the users.
I know that it is possible to get new token with the refresh token but if I take a look in NimbusAuthorizationCodeTokenResponseClient.java the refresh token is not saved anywhere even if it's available.
I don't find any examples on how to keep this refresh token and how to use it, like if it was supposed to work automatically like the whole process.
Can someone have any experience with this Azure Active directory spring boot module?
I'm using Springboot 2.0.4 with azure spring boot module 2.0.5

Your access_token gets automatically refreshed by the refresh_token.
But when your refresh_token token expires you can still run into the same error. To handle it you can make your refresh_token automatically renew the same time you get a new access_token. Use reuseRefreshTokens(false) in the configuration of AuthorizationServerEndpointsConfigurer at the auth-server code:
Take a look at the refreshAccessToken method in the DefaultTokenServices class:
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue,
TokenRequest tokenRequest) {
// Omitted
if (!reuseRefreshToken) {
tokenStore.removeRefreshToken(refreshToken);
refreshToken = createRefreshToken(authentication);
}
// Omitted
}
You should somehow set the reuseRefreshToken flag to false. You can do that in your AuthorizationServerConfigurerAdapter implementation:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
// Other methods
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.reuseRefreshTokens(false);
}
}

Related

How to specify custom return Url after receiving the token or on failure?

I have the following setup:
I'm having an Angular frontend and Spring-Boot backend
Users are logging in to my backend via normal Form login
I'm integrating a third party API which needs oauth2 authentication, so Users need to grant permissions to my App so I can load data on their behalf from that third party
I configured oauth2Client() in my HttpSecurity config to enable oauth2
What currently happens is:
The frontend is calling an endpoint to get data from the third party, lets say /api/get-library which tries to access a protected resource at the third party.
This will lead to a 401 from the third party and trigger the oauth flow in Spring
The User is redirected to the third party to grant permissions to my App
After granting permissions the User is first redirected to the Url I specified as spring.security.oauth2.client.registration.foobar.redirect-uri
Spring Boot then retrieves the token and stores it for my Principal
After that Spring Boot redirects to the original url /api/get-library
But this is just some RestController so the User is presented with some JSON data in the Browser
So point 6 is my problem. I don't want that the User is in the end redirected to some API endpoint, I want him to be redirected to a page of my Angular application.
A similar problem arises if the user rejects the permission grant. Then the user is redirected to spring.security.oauth2.client.registration.foobar.redirect-uri with an query param ?error=true. Also in this case I want a redirect to my Angular application.
Initially I thought I could also configure oauth2Login() which has an failureHandler and successHandler, but those aren't called in my case, since I'm not doing a Login here.
So can somebody help me? How can I configure my own redirects for oauth2Client? In case of success, and on failure? What are relevant Beans here?
I found a solution:
The main Spring class to check is OAuth2AuthorizationCodeGrantFilter. This Filter is invoked when the user granted/rejected the permissions at the OAuth Provider.
Unfortunately there is no way to configure a custom redirect Url for this Filter, so I implemented a hacky solution:
I copied the implementation of OAuth2AuthorizationCodeGrantFilter to an own class and extended it with 2 parameters: success and error return Url. I then used those Urls in the processAuthorizationResponse Method to redirect to my Urls
I then put my ownAppOAuth2AuthorizationCodeGrantFilter before the Spring Filter in the HttpSecurityConfig, so it is used instead of the Spring version
In my Angular App I'm storing the exact location in the App before calling an Endpoint that potentially requires OAuth authentication. So when the User agent returns to the Angular App I can navigate back to the origin location.
It feels very hacky, so if somebody comes up with a better solution I'd be glad to hear it. :-)
Some Code snippets for Spring:
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterBefore(oAuth2AuthorizationCodeGrantFilter(), OAuth2AuthorizationCodeGrantFilter.class);
...
}
#Bean #Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AppOAuth2AuthorizationCodeGrantFilter oAuth2AuthorizationCodeGrantFilter() throws Exception {
return new AppOAuth2AuthorizationCodeGrantFilter(
clientRegistrationRepository,
oAuth2AuthorizedClientRepository,
authenticationManagerBean(),
oauthSuccessRedirectUrl,
oauthErrorRedirectUrl);
}

How to handle OAuth2 access token refresh with synchronous API calls, in Spring Security 5

We are using Spring Gateway (Spring Boot 2.4.6) which uses Spring Security 5 and the Weblux/ reactive model within that to provide OAuth2 security and Keycloak as the IDP.
Refreshing of the Access Token is an issue when our front-end application, which has already [successfully] authenticated against the gateway/ IDP, issues multiple API calls after the session's access token has expired.
It appears that out of (for example) five API calls, only the last one gets re-authenticated against the Keycloak provider and the other four get 'lost', thereby causing issues within the front-end.
If the user refreshes the UI's page then the proper authentication flow happens seamlessly and the token stored in the session is refreshed, without a redirect to the Keycloak login screen (as expected), therefore the problem is only with synchronous API calls.
The SecurityWebFilterChain is setup with:
/*
* Enable oauth2 authentication on all requests, but use our custom
* RegistrationRepository
*/
.and()
.oauth2Login()
.authenticationSuccessHandler(new AuthSuccessHandler(requestCache)) // handle success login
.authenticationFailureHandler((exchange, excep) -> {
LOGGER.debug("Authentication failure: {}", excep.getMessage());
return Mono.empty();
})
.clientRegistrationRepository(clientReg);
// Add our custom filter to the security chain
final KeycloakClientLoginFilter keyclockLogin = new KeycloakClientLoginFilter(
clientReg,
redirectStrategy,
requestCache,
authClientService);
clientReg.setKeycloakClientLoginFilter(keyclockLogin);
http.addFilterBefore(keyclockLogin, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
return http.build();
With the ServerAuthenticationSuccessHandler configured with this:
private class AuthSuccessHandler implements ServerAuthenticationSuccessHandler {
private final ServerRequestCache requestCache;
private final URI defaultLocation = URI.create("/login");
private AuthSuccessHandler(ServerRequestCache requestCache) {
this.requestCache = requestCache;
}
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
final ServerWebExchange exchange = webFilterExchange.getExchange();
return requestCache.getRedirectUri(exchange)
.defaultIfEmpty(defaultLocation)
.flatMap(location -> {
LOGGER.debug("Authentication success. Redirecting request to {}", location.toASCIIString());
return redirectStrategy.sendRedirect(exchange, location);
});
}
}
Within the KeycloakClientLoginFilter there is a ServerWebExchangeMatcher that checks if the required details are present on the inbound exchange, and whether the AccessToken has (or is about to) expire. If it is, it runs through this code to redirect the request off to Keycloak for authentication and/ or refresh:
final ClientRegistration keycloakReg = clientReg.getRegistration(tenantId, appId);
if (!isError && loginRedirects.containsKey(keycloakReg.getRegistrationId())) {
final String contextPath = exchange.getRequest().getPath().contextPath().value();
final URI redirect = URI.create(contextPath + loginRedirects.get(keycloakReg.getRegistrationId()));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("About to redirect to keycloak; for method {}, tenant={}",
exchange.getRequest().getMethod(),
tenantId);
}
// Save the request so the URL can be retreived on successful login
return requestCache.saveRequest(exchange)
.then(redirectStrategy.sendRedirect(exchange, redirect));
}
So, all API calls hit the above code, require a refresh, have their original exchanges saved in the requestCache and are then directed to Keycloak. When Keycloak responds with the updated token, the exchange(s) run through the AuthSuccessHandler, which pulls the original request URL from the requestCache and redirects the call to that original URL.
This part works for web requests and the one in five API calls.
The other four API calls never make it to the AuthSuccessHandler - They simply get 'lost'.
There are some ugly hacks that could be done, like blocking all calls until the one first one is re-authenticated, but that just isn't right and would be hard to get right anyway.
So can the gateway, CookieServerRequestCache or AuthenticationWebFilter only handle one request at a time? Is there a 'simple' implementation of waiting on one call from the same session to re-authenticate?
Any help would be greatly appreciated as the application simply doesn't work (from a user's perspective) until this is resolved.
I know quite some tutorials do so, but in my opinion, authenticating against the gateway is a mistake (see this answer for details why). Why not using an OAuth2 client library on your client(s) instead?
I personnaly use angular-auth-oidc-client, and I am convinced that there must be equivalents for React, Vue, Flutter, Android or iOS.
Such libraries can handle access-tokens refreshing for you (provided that you requested the offline_access scope and that the authorization-server supports refresh-token for your client).
Authenticate users on the client(s) with the help of a certified lib, have your gateway just forward Authorization header and configure your micro-services as resource-servers.

Mobile app authentication for existing API

I have application with Spring security. It's REST api for Angular SPA. It uses session and cookie mechanism. Now I want to access this api from mobile application (Nativescript). I spent some time searching for best way to authenticate mobile app user. In the most cases oauth2 and jwt tokens are advised. So I've done reasearch on this and decided to add additional (seperate) authentication only for mobile application. So Angular app still will be using session with path api/... and mobile app will be using token mechanism with path /api/mobile/... (underneath it will be the same api but with different prefix).
I've decided to use OAuth2 and its Spring integration. I've read documentation and I'm consfused. Why they always mention about authentication provider (Google, Github, Facebook)? I don't want to force my users to login via other service. I want to allow them login with credentials they already registered with in my application. How this social login even related with oAuth authorization server? All examples they've provided use some other services.
I've also tried to add my authorization server in my existing app. I've successfully retrieved token. But now I don't understand all this relationships. There is authorization server that keeps client id and client server. But why /auth/token endpoint needs another authentication? So mobile app needs to provide 3 different credentials - user credentials, client id and secret and token endpoint credentials.
Did I miss something? I know that OAuth is only specification and Spring is implementation of it. But I'm under impression that Spring overcomplicates this. And do I need oauth at all since I have only 1 type of resource?
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.authorizedGrantTypes("refresh_token", "authorization_code", "password", "implicit")
.scopes(scopeRead, scopeWrite)
.resourceIds(resourceIds);
}
}

Preventing need to re-login with Angular6/Spring Boot 2 web app using oauth2

I'm writing a small web-app using Spring Boot 2 as the backend and Angular6/Ionic as the front end. The intention is to have users add the site to their home screen and for it to basically look/feel like a native app. This is working pretty well but I would like to use Google for login with Spring Security Oauth2. My problem is that Spring Boot keeps the user auth tokens on the server associated with the session, and the IOS home screen icon loads with all cookies cleared every time the icon is clicked. Since the cookie is gone when the page loads, the user needs to log in again.
Apparently html5 local storage should persist from launch to launch, so I'm thinking I need generate a key for the user after auth which can be stored in in local storage on the device, then when the the user accesses the page it can provide this key which I can use to "Authenticate" them on the server... something along those lines.
I'm looking for ideas of how to allow the user to stay "Logged in" without the availability of cookies being reliably stored for any length of time.
Currently Using
Spring Boot 2
Angular 6
Ionic 4
Spring Security
Spring Oauth2
Everything is behind security except for the login page.
At the moment I'm persisting sessions to jdbc and my configs look like this:
applicaion.yml
spring.security.oauth2.client.registration.google.client-id=XXXXX
spring.security.oauth2.client.registration.google.client-secret=XXXX
server.servlet.session.persistent=true
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
MvcConfig.java
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static");
registry.addResourceHandler("/static/*.js", "/static/*.css", "/static/*.svg")
.addResourceLocations("/static")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
SecurityConfig.java
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login().and().logout().logoutSuccessUrl("/");
}
}
I've tried telling spring to put session ID's as x-auth headers, but google oauth appears to stop working then. As in I go to a page, get the screen to click on to log in with google, log in with google and am returned to my login page with an error: "Your login attempt was not successful, try again."
So basically google oauth works with config above but fails with this added to SecurityConfig.java
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return new HeaderHttpSessionIdResolver("X-Auth-Token");
}
This is apparently failing because The appropriate session information isn't getting passed to/back from Google. My login process produces 3 "sessions"
1) When the user first tries to access the page and gets the login page
2) When the token response is returned from google. This session indicates an error of "authorization_request_not_found"
3) When the user is re-directed back to the login page.
It looks like some info about the session is being passed to/back from google but the session ID's doing look right
Request to google auth is:
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=1111111111111-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com&scope=openid%20profile%20email&state=NGW6kTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%3D&redirect_uri=http://localhost.com:9733/login/oauth2/code/google
Callback from google auth:
http://localhost:9733/login/oauth2/code/google?state=NGW6kTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%3D&code=4/xxxx_xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&scope=openid+email+profile+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/plus.me+https://www.googleapis.com/auth/userinfo.email&authuser=0&session_state=6ee92xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..2618&prompt=none

Spring boot, oAuth2 sign up with facebook

I have been following #Dave Syers' excellent tutorial on Spring boot and oAuth2
I have been able to create a log in function, so that protected resources need a login to facebook before they can be accessed.
But now I am trying to create a "sign up" page. On stackoverflow, for example, there is an option to sign up with facebook, so your details are sent to Stackoverflow.com from facebook. How can this be performed with oAuth2? I was able to do this with spring-social, but I cannot wrap my head around how to do this with a direct oauth2 approach.
Please help?
The answer was simpler than I expected. All I needed to do was add my custom AuthenticationSuccessHandler to the filter:
All I had to do was add an AuthenticationSuccessHandler handle to the method that returns a Filter ssoFilter()
#Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()));
facebookFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
return facebookFilter;
}
And my CustomAuthenticationSuccessHandler was just a component that extended AuthenticationSuccessHandler
#Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//implementation
}
}
So in my sign up page, I could simply use the same login action, but in the success handler I created the User and stored her in the DB
Make a integrated jwt-oauth2-signup-login is difficult. There are some easy way:
1, to use satellizer-spring-boot, or satellizer.
2,to use spring social.
3, add jwt to spring oauth2 as separate provider:
This is how to do with 3:
I have not use signup+oauth2 yet(Because I like spring social and it can do same function), but in theory it can be done in a very easy and can be done as follow:
First, when user login (Register on facebook will also lead to login page) form facebook, just import the user's information and write the information to user model. It is can be done with a controller and a view.
On front page, it is easy to make user choose to login, or register a new account: As Spring boot support multiple filter and multiple AuthenticationProvider,That means you can use two filters, one for oauth2,and another (jwt local server) filter for local server register.
1,download a standard spring boot jwtFilter.java file and put it in your config directory.
2,Make a controller for register new user.
3, make a /login to return jwt token.
3, make two filter, one for oauth2, one for local jwt.
4, make a Sign up link to /register. and a login tag link to /login.
ps: you can copy all the lines form a standard spring boot jwt project, here is one: https://github.com/mrmodise/senepe

Resources