Spring Authorization Server: how to continue authorization after login - spring-security

I am trying to integrate Authorization Server into my app with form login. I'm using my own login page.
Samples suggest to use the following configuration:
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
//...
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) ->
exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
//...
)
Now when I try to authorize (/oauth2/authorize?...) I got redirection to my login page. If I logged in before I see OAuth consent page and able to submit consent. But 99% of times I see my /login page, able to log in and stuck here. How to continue to consent page there? Should I write my own logic for that?

Solved issue myself by removing custom .successHandler(...) from my custom form login configuration. Default SavedRequestAwareAuthenticationSuccessHandler correctly handle all redirects as expected.

Related

Logout at Spring Authorization Server doesn’t terminate user session in Keycloak

I am trying to implement an end-user initiated logout mechanism.
I have 4 main entities involved.
Client: an Angular app registered as a public OIDC client on Keycloak
Keycloak: behaves as an identity broker
Spring Authorization Server: identity provider registered on Keycloak
Resource Server: a Spring Boot application with a secure REST endpoint
When the end-user initiates a logout, a call is made by the Angular application to Keycloak’s end_session_endpoint. I have configured the logout URL for my identity provider (Spring Authorization Server) in Keycloak as http://localhost:9000/logout which is the default Spring Security logout endpoint.
FYI: I haven't enabled BackChannel or FrontChannel logout.
In the Network tab of Developer Console, the sequence of calls happen as below:
http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/logout?client_id=hello-world&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A4200%2F&id_token_hint=...
http://localhost:9000/logout?state=ff57bb5b-4a79-4e68-bd39-fe7a33ec8788&id_token_hint=...&post_logout_redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Fauth%2Frealms%2FSpringBootKeycloak%2Fbroker%2Foidc%2Fendpoint%2Flogout_response
While inspecting the DEBUG logs in the Spring Authorization Server, I am able to see the logout happen for that particular end-user including invalidating the JSESSIONID, however the session doesn’t terminate in Keycloak which causes the user to stay logged in and access the secure REST endpoint.
Is the Spring Authorization Server expected to return a specific response back to Keycloak to convey that the logout process is complete at it’s end and that Keycloak can end the session now?
This is my logic for logout at Spring Authorization Server.
#Bean
#Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
authorize -> authorize.mvcMatchers("/hello/**").permitAll().anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll())
.addFilterAfter(cookieFilter, ChannelProcessingFilter.class)
.logout().logoutSuccessUrl("http://localhost:4200");
return http.build();
}
I am totally clueless about what I am missing to make Keycloak end the session at it’s end.
Any leads will be greatly appreciated.
Thank you!
Update: A post_logout_redirect_uri parameter is sent in the logout request to the Spring Authorization Server. I am not able to see a redirection happen to this URI which may be the reason that Keycloak session doesn't terminate.
post_logout_redirect_uri=http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/broker/oidc/endpoint/logout_response
Got this to work finally!
The logoutSuccessUrl("http://localhost:4200") is incorrect.
As I mentioned in the Update part of my question that Keycloak sends a post_logout_redirect_uri parameter (inspected the logout endpoint in the Network calls in Chrome Developer Console), the logoutSuccessUrl or the redirectUri should be set to
http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/broker/oidc/endpoint/logout_response
Also, note that the above URL takes in state as a request parameter. Initially, it gave me a 400 Bad Request as I didn't send the state.
Since I was required to intercept the state parameter when logout is initiated at the Spring Authorization Server, I added a custom logout handler to do the job.
#Component
public class IdpLogoutHandler implements LogoutHandler {
private static final String STATE = "state";
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String state = request.getParameter(STATE);
try {
response.sendRedirect(
"http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/broker/oidc/endpoint/logout_response?state="
+ state);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Updated WebSecurityConfig:
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
authorize -> authorize.mvcMatchers("/hello/**").permitAll().anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll())
.addFilterAfter(cookieFilter, ChannelProcessingFilter.class).logout()
.addLogoutHandler(customLogoutHandler);

How to incoporate facebook login MVC 5 identity 2?

In an account controller there is ExternalLogin action.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
I have appId and appSecrete set in StartUp. I'm running my application in local machine(localhost:port). I have custom facebook button(normal button).
I do not understand how I can call the external login from the button since the action above requires provider and return url. There is already ExternalLogin callback action as well.
When setting facebook application, I seem not to understand this part: Valid OAuth redirect URIs.
Please somebody help. The articles I read confuse me in this regard.
Edit: I can now connect to facebook, the only problem is I get an error:
URL Blocked: This redirect failed because the redirect URI is not
whitelisted in the app’s Client OAuth Settings. Make sure Client and
Web OAuth Login are on and add all your app domains as Valid OAuth
Redirect URIs.
I believe it is caused by Valid OAuth redirect URIs which is not set correctly.
Go to you facebook app development website. Open your page. Go to Facebook Login option under it go to setting option and change your Valid OAuth redirect URL. you have to give your website valid path in that section.
For local host you can try like this -
http://localhost:8080/fb_login_local/

Google Middlewire Authentication in MVC Core

I was trying to add google authentication in my MVC application.
I followed all the steps and registered the middlewire in Configure method in Startup.cs
app.UseGoogleAuthentication(new GoogleOptions
{
ClientId = Configuration["Google:ClientID"],
ClientSecret = Configuration["Google:ClientSecret"],
Scope = { "email", "openid" }
});
I am saving ClientID and ClientSecret in appsettings.json.
In developers.google.com I am using G+ api for signin purposes.
using https://localhost:44386 as Authorized JavaScript origins
and https://localhost:44386/signin-google as Authorized redirect URIs
The issue is the page shows the Allow and Deny access page and upon clicking on the Allow ,the page stays in loading state and never redirects to ExternalLoginConfirmation.cshtml page.
Using default single user authorization mvc core template for this. Any help where I am doing wrong.
This is the OUTPUT stream from my MVC Core app.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:61356/signin-google?state=CfDJ8MgRzE29TklJksJpDlbDmWeE9W4z9qKrqyko5mov7FHppD2rTlEeUUBP0nFXZ-edch6RmiDobEj-88mvuEG1a_-X087pVbicROYtDDBl1MnQyMxGaN7pImaOhrA_c5XCpRgR3G73GVME-Qk4km2tqO5iP-25ccTwySQYmDd3ham2pmWO5CBwoABnavk9Tu0Ym2XOoWRstcuGEUSqecuj8fLGRMjyBmrBW-mTSXDuKrHu6kLg7kUze48fyjg2o9BHq68JS5F3SA9_q2bqxr2DKg4MrY5vmvJoADmcUAcE6MFONEeQCgEm0j71cdFuIaw46g&code=4/F179kp-WOfRd88aBQPdRsSfynoRwES3gUn7bhHvCw0A&authuser=0&hd=motorolasolutions.com&session_state=f02757ef58689502dbceb4b6cc6bd131dfa2d805..5a04&prompt=consent
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware:Information: Cookies was not authenticated. Failure message: Unprotect ticket failed
Its saying Cookies was not authenticated. Failure message: Unprotect ticket failed.

Single Logout second request fails in ADFS

I have setup when I'm using Auth0 to SSO through ADFS into a Spring Security Application, using saml all the way through. So, it looks like this:
Auth0 ----> ADFS ----> SpringSecurity App.
I have control of Auth0, but it's simulating a third party that would integrate with our ADFS server.
This works fine.
To do logout, I minimally want to destroy the sessions on the SpringSecurity App and the ADFS application. When I call logout the first time, a SAML logout request gets passed to ADFS. ADFS then passes a Logout request to Auth0, where it ends. This destroys the session.
However, if, without closing the browser, I SSO in again and then logout, ADFS rejects the logout request with a urn:oasis:names:tc:SAML:2.0:status:Requester, meaning it didn't like something about my request.
I've been able to narrow it down to the fact that there is a SAMLLogout cookie, with a domain of my ADFS server set. This appears to be set when the logout bounces over to Auth0, but is never removed. Calling logout when that cookie is present causes an error. When I delete that one cookie, I can logout successfully (in that it destroys the ADFS session and sends a logout request to Auth0). The cookie has a Session expiration, so closing and opening the browser also works.
I can post token requests and responses, but I don't think it has to do with the tokens itself. They are all signed correctly, and ADFS doesn't report any errors when doing the logout.
I don't necessarily need ADFS to call out to Auth0 (or any IdP) to destroy that session, I simply need it to destroy it's own session.
Instead of using destroy kindly use "unset" to avoid further problems with regards to your log out code. I don't know why but there are times that "destroy" causes problems with regards to log out button.
We came up with the following solution:
we have noticed that the adfs/ls/idpinitiatedsignon.aspx page has the "Sign out from all the sites that you have accessed." option that will kill the authentication cookies no matter how many times you logged in and logged out from different tabs.
We have create a duplicate copies of the idpinitiatedsignon.aspx and the idpinitiatedsignon.aspx.cs and renamed them (example logoutpage.aspx).
We have added the SingleLogout(); at the end of the Page_Init of the idpinitiatedsignon.aspx.cs so it reads:
protected void Page_Init( object sender, EventArgs e )
{
string rpIdentity = Context.Request.QueryString[RpIdentityQueryParameter];
//
// If the query string specified a certain relying party, sign in to that relying party.
//
if ( !String.IsNullOrEmpty( rpIdentity ) )
{
string decodedIdentity = Server.UrlDecode( rpIdentity );
if ( decodedIdentity == IdpAsRpIdentifier )
{
decodedIdentity = String.Empty;
}
SignIn( rpIdentity, new SignOnRequestParameters() );
}
else
{
PopulateConditionalVisibilityControls();
RelyingPartyDropDownList.DataSource = RelyingParties;
RelyingPartyDropDownList.DataBind();
UpdateText();
}
SingleLogout();
}
then we referenced this new page example logoutpage.aspx as the logout URL.
I hope this will save some research time for others who are facing this SAML Logout issue on ADFS v2.

Testing spring security with Postman

I have configured a form-base authentication using Spring security. It works fine when I log in using the login form in my web application.
It also works with cURL:
curl --data "j_username=myname&j_password=mypassword" http://localhost:8080/test/j_spring_security_check --verbose
However, I cannot make it works using Postman:
What is missing? I need to be authenticated in order to test other services.
Finally I managed making Postman aware of authentication by using the Postman Interceptor chrome extension
With the extension enabled (by clicking the satellite icon within the Postman App), you just need to log in using the login form of your web and then you can start using services that require authentication in Postman.
You can achieve authentication/authorization in postman through various authorization types given in postman dropdown under Authorization tab.
Below is the step to use Basic Auth which by default spring security provides.
In spring security you can customize your credentials in application.properties file as given below.
spring.security.user.name=yer
spring.security.user.password=galem
Using http.httpBasic worked for me.
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/api/home")
.hasRole("ADMIN")
.antMatchers("/api/product/*")
.hasRole("ADMIN")
.and()
.formLogin();
When using basic auth on postman, you will set the credentials on the authorization tab.
Click on Authorization, choose the type as Basic Auth, the credentials section will be displayed for you to key in the username and password.
For me the Postman Interceptor was not working, So I did the following and now I can login to the server.
Select POST request from dropdown and type login URL in request URL section.
Select Body from tabs
Enter username and password keys and values as shown in picture.
And click send this will create a temp cookie in postman and be there for a session.
You can also do GET request for logout URL to logout from session. Or delete the cookie from Cookies below Send button.
http.httpBasic is working for Testing spring security with Postman.
For example, Added the override configure method from the configuration class MyConfiguration.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll().anyRequest().authenticated()
.and().formLogin().permitAll().and().logout().permitAll().and().httpBasic();
http.cors().disable().csrf().disable();
}
Note: To access the POST, PUT, and DELETE request you need to disable the cors and csrf as per code above.

Resources