WSO2 and Spring SAML single logout issue - spring-security

The issue I'm having has been discussed several times on this site, however most posts are quite old. Here is something similar to what I'm experiencing. I'm using WSO2 IS 5.2 as my IdP and I have 2 java based web applications hosted on difference servers that participate in SSO. Each webapp (SP) has implemented the Spring-SAML extension. Single Sign-On works perfectly but Single Logout only partially works. Here is the test case:
Access secure resource on webapp1
Login page from Idp (WSO2) is presented and user logs in
Secure resource from webapp1 is presented
Access secure resource on webapp2
SAML request is sent to Idp, Idp responds and user is authenticated
Secure resource from webapp2 is presented, end SSO
Initiate single log out from webapp2
Webapp2 send saml request (through browser) to Idp and saml response is returned
User is logged off locally on webapp2 and Idp session is terminated
The IdP directly sends logout request to webapp1 (back-channel type)
Logout request fails to webapp1 (log indicate SamlStatusException: No user is logged in)
So the end result is that I still have a local session on webapp1. If I change the order and initiate SLO from webapp1, then webapp1 will be logged out and webapp2's local session will continue to exist. The WSO2 server is able to determine the 2nd session participant during SLO, however the HTTP request sent from the Idp to the 2nd session participant does not have a Spring security context. This would be a stateless HTTP request so there wouldn't be a logged in user. This is why I believe it is failing.
I found this discussion. Its about 2 years old. Is there anything new on this issue? Maybe a configuration step missed by me on WSO2 or in the Spring-saml config.
Here is a relevant piece of my SP metadata:
<md:SingleLogoutService Location="https://tpap10-wwwdev04.arbfile.org:443/webapp/saml/SingleLogout" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"/>
<md:SingleLogoutService Location="https://tpap10-wwwdev04.arbfile.org:443/webapp/saml/SingleLogout" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
<md:AssertionConsumerService Location="https://tpap10-wwwdev04.arbfile.org:443/webapp/saml/SSO" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" isDefault="true" index="0"/>
<md:AssertionConsumerService Location="https://tpap10-wwwdev04.arbfile.org:443/webapp/saml/SSO" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" index="1"/>
Some relevant Spring-Saml config on the SP:
<bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
<constructor-arg>
<bean class="org.springframework.security.saml.metadata.MetadataGenerator">
<property name="entityId" value="urn:webapp1:mycity"/>
<property name="entityBaseURL" value="https://wwwdev04.domain.org:443/webapp" />
<property name="extendedMetadata">
<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
<!-- <property name="signMetadata" value="false"/> -->
<property name="idpDiscoveryEnabled" value="false"/>
</bean>
</property>
</bean>
</constructor-arg>
</bean>

WSO2 was not implementing the SAML 2.0 specification correctly. The specification requires that Single Logout with HTTP-* bindings is done using front-end channel (= through user's browser) - which makes the HTTP session available and allows Spring SAML to terminate it correctly. I believe that this issue was never fixed in WSO2.
Spring SAML uses HttpSession for storage of user's state by default. This is also the reason why Spring SAML doesn't support Single Logout with SOAP binding out of the box. It would be possible to implement an application-wide storage of Spring Security sessions which could be invalidated independently from the HttpSession (and therefore work-around the WSO2's limitation), but this is not configured by default (and I have never tried it).

Related

Configuration of Microsoft Graph OAuth2 authentication in Spring Security - Error AADSTS90014

I am writing an SSO provider for MS Graph APIs Azure AD v2 endpoint leveraging Spring OAuth2.
I am progressing with the implementation and constant testing but I stumbled upon an error returned by AAD which is puzzling me. After all, this should all be plain standard OAuth 2 flow.
I successfully configured my application on MS dev portal, providing a localhost redirect URL (which, for the record, is the only supporting the http scheme. Kudos to MS). So when I invoke http://localhost/myapp/auth/office365 Spring security successfully intercepts the invocation, provides a correct redirect to my browser with client ID to https://login.microsoftonline.com/common/oauth2/v2.0/authorize with expected parameters.
Microsoft shows a consent screen to me, after which I get redirected back to my Spring Security application via HTTP GET with expected authorization code parameter.
The problem is that when the application tries to negotiate the given authorization code for a bearer token headaches start. Spring Security invokes a POST to https://login.microsoftonline.com/common/oauth2/v2.0/token but ends in 401 error.
Here is the stack trace
error="invalid_request", error_description="AADSTS90014: The request body must contain the following parameter: 'client_id'.
Trace ID: 9acd2a10-1cfb-443f-9c57-78d608c00c00
Correlation ID: bf063914-8926-4e8f-b102-7522d0e3b0af
Timestamp: 2017-10-09 15:51:44Z", correlation_id="bf063914-8926-4e8f-b102-7522d0e3b0af", error_codes="[90014]", timestamp="2017-10-09 15:51:44Z", trace_id="9acd2a10-1cfb-443f-9c57-78d608c00c00"
at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:100)
at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:33)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:235)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readInternal(AbstractJackson2HttpMessageConverter.java:215)
at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:193)
at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:235)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:621)
at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:137)
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:209)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:148)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:121)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:105)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
I have looked into Spring security implementation to find the cause,.
It happens that the error message error="invalid_request", error_description="AADSTS90014: The request body must contain the following parameter: 'client_id'. is self explanatory: MS Graph wants the client ID (which is still supplied by the basic authentication header) in the request body. Stop for a moment. I want to use plain old Spring Security and not third-party specific jars in order not to pollute my classpath.
Looking into Java source code of Spring OAuth 2 the problem is damn clear. Spring uses the client ID only in getParametersForAuthorizeRequest, which is used to generate the redirect URL. When it comes to getParametersForTokenRequest the client ID is not specified in the form.
Question: who is right here? How do I tell Spring that MS wants the client id in the token request after an authorization code has been obtained?
Just to clarify, you're not actually authenticating with or against Microsoft Graph. You're actually authenticating against Azure Active Directory. The Microsoft Graph API accepts the bearer token you'll end up with but it doesn't issue the access token itself.
It isn't clear which endpoint you're using for the Authorization Code flow, AAD has two of them: v1 and v2. The primary difference being that v2 uses a central registration and can authenticate both work/school and personal accounts.
Regardless of the endpoint, you do need to supply the clientid in the request body when you're requesting an access token. There are actually several values you need to provide in the body. Also note that these need to be provided as application/x-www-form-urlencoded.
For the v1 endpoint you provide (line breaks for readability only):
grant_type=authorization_code
&client_id={client-id}
&code={authoization-code}
&redirect_uri={redirect-uri}
&client_secret={client-secret}
&resource={resource-uri}
The v2 endpoint is almost identical but uses scope instead of resource:
grant_type=authorization_code
&client_id={client-id}
&code={authoization-code}
&redirect_uri={redirect-uri}
&client_secret={client-secret}
&scope={scopes}
OP's edit
Now, back to Spring Security. Spring by default uses an HTTP basic authentication scheme against Azure AD. In that scheme, the client ID and secret are encoded into the HTTP Authorization header, then the form only contains the authorization code and state parameter, so here is why I (the OP, ndr) was puzzled about why AAD refused the authorization.
In order to pass client ID and secret into the form, we can tell Spring Security to use a different supported authentication scheme. The form authentication scheme will push the client ID and secret into the form.
The below code works and retrieves the access token.
<oauth2:resource
id="msAdAuthenticationSource"
client-id="${oauth.appId}"
client-secret="${oauth.appSecret}"
type="authorization_code"
authentication-scheme="form"
client-authentication-scheme="form"
use-current-uri="true"
user-authorization-uri="${oauth.authorizationUri}"
access-token-uri="${oauth.accessTokenUri}"
scope="${oauth.scopes}"
pre-established-redirect-uri="${oauth.redirectUri}" />
Please note the two
authentication-scheme="form"
client-authentication-scheme="form"
Problem solved, a lot more to come!

OAuth2.0 Credentials from Mule Service

I want to pass OAuth2.0 authorization credential from my Mule service flow to integrate it with FitBit API.
When I am trying to consume FitBit API from PostMan, there is a provision of "Get New Access Token" which asks user to pass
1) Auth URL 2) Access Token URL 3) Client ID 4) Client Secret (FYI -all these credentials would be generated by FitBit).
My issue here is, I am not sure how these credentials would be passed from Mule?
Can any one please help me to provide some pointer please?
I think your situation is the same as the Github API example in the MuleSoft docs.
Configure the Mule client app for accessing the FitBit API like the example shows for accessing the Github API:
<http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration" basePath="/github"/>
<http:request-config name="HTTP_Request_Configuration" protocol="HTTPS" host="api.github.com" port="443" doc:name="HTTP Request Configuration">
<oauth2:authorization-code-grant-type clientId="27...df" clientSecret="ae...6" redirectionUrl="http://localhost:8082/callback">
<oauth2:authorization-request authorizationUrl="https://github.com/login/oauth/authorize" localAuthorizationUrl="http://localhost:8082/login" />
<oauth2:token-request tokenUrl="https://github.com/login/oauth/access_token">
<oauth2:token-response accessToken="#[payload.'access_token']" refreshToken="#[payload.'access_token']"/>
</oauth2:token-request>
</oauth2:authorization-code-grant-type>
</http:request-config>
<flow name="oauth-grant-codeFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/" doc:name="HTTP"/>
<http:request config-ref="HTTP_Request_Configuration" path="/user" method="GET" doc:name="HTTP">
<http:request-builder>
<http:header headerName="Accept" value="application/vnd.github.v3+json"/>
</http:request-builder>
</http:request>
<dw:transform-message doc:name="Transform Message">
<dw:set-payload><![CDATA[%dw 1.0 %output application/json
---
payload]]>
</dw:set-payload>
</dw:transform-message>
</flow>
I was also getting the same error while trying to connect with Channeladvisor API.This error comes only when you try to use HTTPS . The solution of this issue is to configure you TLS/SSL setting. Install new certificate for the same.
Thanks,
-Dev

Azure website can't login to AAD

I made a mvc web app based on this example:
https://github.com/OfficeDev/PnP/tree/master/Samples/MicrosoftGraph.Office365.Generic/OfficeDevPnP.MSGraphAPIDemo
Strange thing is when I work from localhost then all the auth2 authentication works. When I deploy on Azure this happens:
On first page call on Azure I get the response:
An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.
On second page call the message:
IDX10803: Unable to create to obtain configuration from: '__AADInstance____AuthenticationContext__/.well-known/openid-configuration'.
I've already seen this post on SO, but I've already set the tenant GUID in the configuration and the AAD privileges are "read directory data" and "sign in and read user profile"
Are there any configuration options on Azure maybe in the new management portal that I should grant?
I have a single tenant addin and this keys in web.config:
<add key="ida:AADInstance" value="https://login.microsoftonline.com/" />
<add key="ida:AuthenticationContext" value="https://login.microsoftonline.com/common" />
<add key="ida:TenantId" value="GUID..." />
<add key="ida:PostLogoutRedirectUri" value="https://localhost:44300/" />
<add key="ida:MicrosoftGraphResourceId" value="https://graph.microsoft.com" />
I tried the section authorization/authentication in the new management portal to enable app service authentication with aad
and could enter the first site of the addin, but the authorization code from my addin was not called and specific file functions don't work if you rely on the portal authorization.

How to send specific response in intercept-url?

I am new to spring. We are building a Mobile-app for our client. The mobile-app will interact with our rest services. But initially, the user should login to the mobile app and then will be able to access the other services.
I have built a Login service which will validate the user credentials with ldap and create the session and the subsequent requests to other services could be consumed.
But if the services are requested anonymously, a specific response object need to be returned.
Currently with below configuration, I am able to return the response code 403.
<beans:bean id="preAuthenticatedProcessingFilterEntryPoint"
class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
<security:http use-expressions="true" entry-point-ref="preAuthenticatedProcessingFilterEntryPoint">
<security:intercept-url pattern="/scheduling/**" access="isAuthenticated()" />
</security:http>
But what would be required to respond with a error message in the response body.
I tried to implement the re-direct url, in EntryPoints. But I am not comfortable as the request should not be redirected.
I have a default error Response, which I would need to implement if the anonymous user tries to access services
<ErrorInfo>
<message>Access Denied</message>
<status>403</status>
<url>requested URL</url>
</ErrorInfo>
If I get it right, you want to adjust the response that is currently returned in case of unauthenticated access. In such case you need to implement and register your own AuthenticationEntryPoint or extend Http403ForbiddenEntryPoint you are currently using and override commence method to return response containing also required body.
<beans:bean id="yourCustomEntryPoint" class="com.example.YourCustomEntryPoint" />
<security:http use-expressions="true" entry-point-ref="yourCustomEntryPoint">
<security:intercept-url pattern="/scheduling/**" access="isAuthenticated()" />
</security:http>

Pre-Authentication / Single SignOn using Spring Security

I am developing two web applications where one is a server app and the other is a client app, both are using Spring Security. My use case is such that after a user logs into the server app, the user can then access the client app from links within the server app. Since the user should not have to log in again when they click on these links (part of my requirements), I decided to use a strategy similar to Single SignOn in order to forward their authentication information from the server app to the client app.
On the client app, I am using Spring Security's RequestHeaderAuthenticationFilter to look for a custom request header that is set by the server app.
If this custom header is found, do I have to do any further validation that this request is trustworthy? In Spring's Pre-Authentication doc, RequestHeaderAuthenticationFilter does not perform any authentication and will assume the request to be from the user specified in the SM_USER attribute. How do I ensure that the request is genuine?
How do I send the user from one app to another with a custom header in the http request? Redirecting the request does not work as the header information will be lost. Forwarding does not work as the forwarded request does not go through the configured Spring Security filters on the client app, thus the request is never "authenticated" and no session is created.
Since I didn't receive any responses, I changed my approach slightly in order to achieve the same SSO behavior. I am answering my own question here to close this issue.
Instead of using the RequestHeaderAuthenticationFilter, I subclassed Spring's AbstractPreAuthenticatedProcessingFilter which retrieves the Principal and Credentials from the HttpRequest. I then implemented a custom preAuthenticatedUserDetailsService that will validate the Credentials with the server app before loading the UserDetails.
As for #2, I am no longer using custom headers in the initial pre-authenticated login request. I am simply appending the principal (username) and credentials as url parameters to the initial pre-authenticated "login" request to the client app. Since the communicated between the two apps are secured via SSL, I figured that should be safe.
This is what my security configuration looks like now:
<b:bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
<b:bean id="navigatorPreAuthFilter" class="com.example.NavigatorPreAuthenticatedProcessingFilter">
<b:property name="authenticationManager" ref="authenticationManager" />
</b:bean>
<http auto-config="false" entry-point-ref="http403EntryPoint">
<custom-filter position="PRE_AUTH_FILTER" ref="navigatorPreAuthFilter" />
<session-management session-fixation-protection="newSession" />
<logout logout-success-url="/logout" delete-cookies="JSESSIONID" />
<intercept-url pattern="/index.jsp" access="ROLE_QUESTIONNAIRE_ASSIGNEE"/>
</http>
<b:bean id="preAuthenticatedUserDetailsService" class="com.example.NavigatorPreAuthenticatedUserDetailsService" />
<b:bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<b:property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService" />
</b:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="preauthAuthProvider"/>
</authentication-manager>

Resources