How To properly OIDC Logout in WSO2 IS - wso2

I am trying to logout from an application that is using OIDC for the authentication. Once Am logged in I can not logout when I head to /logout am not seeing the consent page that am used to see when logging out from the WSO2 Console application(I haven't disabled it so it should appear to confirm the logout). after that I am redirected to the /login page in which am not required to insert credentials and all I have to do is click allow on the consent.
Config security class
public class ConfigSecurity extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login","/assets/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login().loginPage("/login")
.and()
.logout().logoutUrl("/logout")
.logoutSuccessHandler(oidcLogoutSuccessHandler());
}
#Autowired
private ClientRegistrationRepository clientRegistrationRepository;
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(
this.clientRegistrationRepository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri(URI.create("http://localhost:8844/logout"));
return oidcLogoutSuccessHandler;
}
}
Callback URI :
regexp=(http://localhost:8844/login/oauth2/code/wso2|http://localhost:8844/logout)
BackChannel Logout URI : https://localhost:9443/oidc/logout
Application.properties :
server.port=8844
#########
spring.security.oauth2.client.registration.wso2.client-name=WSO2 Identity Server
spring.security.oauth2.client.registration.wso2.client-id=5YvGdwKZaS6pTS_uZhfu_X8sNVYa
spring.security.oauth2.client.registration.wso2.client-secret=hGPrgFnlbuS5N7_srxRenz998h8a
spring.security.oauth2.client.registration.wso2.redirect-uri={baseUrl}/login/oauth2/code/wso2
spring.security.oauth2.client.registration.wso2.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.wso2.scope=openid
# spring.security.oauth2.client.provider.wso2.issuer-uri=https://localhost:9443/oauth2/oidcdiscovery
#Identity Server Properties
spring.security.oauth2.client.provider.wso2.authorization-uri=https://localhost:9443/oauth2/authorize
spring.security.oauth2.client.provider.wso2.token-uri=https://localhost:9443/oauth2/token
spring.security.oauth2.client.provider.wso2.user-info-uri=https://localhost:9443/oauth2/userinfo
spring.security.oauth2.client.provider.wso2.jwk-set-uri=https://localhost:9443/oauth2/jwks
Can anyone help
thanks in advance

Springboot oauth client derives OIDC logout endpoint of the IDP from the discovery endpoint. The issue is, from your application properities file, the application could not find the logout endpoint of the IDP. Token endpoint, authorization url, user-info-uri and jwk-set-uri can be configured separately. But there is no way to configure logout url in such a way. Since WSO2 supports OIDC discovery, all the endpoints token endpoint, authorization url, user-info-uri and jwk-set-uri urls, logout endpoint can be obtained from the issuer_uri property. So remove Token endpoint, authorization url, user-info-uri and jwk-set-uri configurations and add issue-uri config. Apply the below configuration to your properties file and see.
server.port=8844
#########
spring.security.oauth2.client.registration.wso2.client-name=WSO2 Identity Server
spring.security.oauth2.client.registration.wso2.client-id=5YvGdwKZaS6pTS_uZhfu_X8sNVYa
spring.security.oauth2.client.registration.wso2.client-secret=hGPrgFnlbuS5N7_srxRenz998h8a
spring.security.oauth2.client.registration.wso2.redirect-uri={baseUrl}/login/oauth2/code/wso2
spring.security.oauth2.client.registration.wso2.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.wso2.scope=openid
spring.security.oauth2.client.provider.wso2.issuer-uri=https://localhost:9443/oauth2/token
You can refer these docs:
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-security-oauth2-client
https://www.baeldung.com/spring-security-openid-connect

Related

Redirect URI mismatch error from Google OAuth

I have a Flask web application which is hosting in Google Cloud Run which is hosted with https://mydomain.run.app.
Now I am trying to add google authentication to it. I have created the API under credentials in GCP. I have given https://mydomain.run.app/authorize in the redirect uri but when I tried to login from my app it throws me redirect mismatch error.
And the error shows me http://mydomain.run.app/authorize.
The mismatch is the https and http
When I tried to give http in the credentials uri it throws me
Invalid Redirect: This app has a publishing status of "In production". URI must use https:// as the scheme.
#app.route('/login/google')
def google_login():
google = oauth.create_client('google')
redirect_uri = url_for('authorize', _external=True,_scheme='https')
return google.authorize_redirect(redirect_uri)
#app.route('/authorize')
def authorize():
google = oauth.create_client('google')
token = google.authorize_access_token()
resp = google.get('userinfo')
user_info = resp.json()
user = oauth.google.userinfo()
session['profile'] = user_info
session.permanent = True
return redirect('/select')
Under Authorized redirect URIs
You should put 1 more URI :
https://mydomain.run.app/
Then check again. I have got same issue before.
your app is currently set to production in google developer console.
This means that all of the redirect uris you try to add to your project. Must be HTTPS and not HTTP you can also not use localhost
As you are trying to use http://mydomain.run.app/authorize you need to change it so that it is https://mydomain.run.app/authorize note that the first one was http:// and not https://
The error is coming because your application itself is trying to send a redirect uri of http and not https. You need to fix your application so that it is using https.

WSO2 ApiManager 3.2 - Adaptive Authentication function assignUserRoles is not defined

I am trying to using an openAM external identity provider to authenticate users of the WSO2 developer portal.
To do that I need to dynamically add the role Internal/subscriber to user authenticated via openAM because we don't have roles in openAM.
So I added the following code to the Script Based Adaptive Authentication:
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step.
var user = context.currentKnownSubject;
assignUserRoles(user, ['Internal/subscriber']);
}
});
};​
Trying the authentication I see in the wso2 logs the error "assignUserRoles" is not defined:
TID: [-1234] [] [2021-06-10 10:57:34,273] ERROR {org.wso2.carbon.identity.application.authentication.framework.config.model.graph.JsGraphBuilder} - Error in executing the javascript for service provider : apim_devportal, Javascript Fragment :
function (context) {
// Extracting authenticated subject from the first step.
var user = context.currentKnownSubject;
assignUserRoles(user, ['Internal/subscriber']);
} <eval>:4 ReferenceError: "assignUserRoles" is not defined
at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:57)
at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:319)
at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:291)
at jdk.nashorn.internal.objects.Global.__noSuchProperty__(Global.java:1442)​
Any idea on how to solve this? Or any other alternative to give a default Internal/subscriber to any user authenticated via OpenAM?
The above-mentioned error is expected in the API Manager servers. This is because the API Manager servers are not built to support Adaptive Authentication as WSO2 IS servers.
So, if you are planning to perform the Adaptive Authentication, the best option would be to deploy a WSO2 IS server as a Key Manager with API Manager and perform the task. Further, as an alternative way, we can implement a custom Provisioning Handler to assign the `Internal/subscriber' role to the provisioning users.
You can refer to the SystemRolesRetainedProvisionHandler.java implementation for more clarity. We can make use of the retrieveRolesToBeDeleted() to append the Internal/subscriber role into the rolesToAdd variable and then configure the custom provisioning handler in the API Manager with the following TOML config
[authentication.framework.extensions]
provisioning_handler = "com.sample.custom.CustomRoleProvisioningHandler"
A sample implementation is given below
// CustomRoleProvisioningHandler.java
import java.util.List;
import java.util.Map;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.impl.DefaultProvisioningHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.impl.SystemRolesRetainedProvisionHandler;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.UserStoreException;
public class CustomRoleProvisioningHandler extends SystemRolesRetainedProvisionHandler {
#Override
public void handle(List<String> roles, String subject, Map<String, String> attributes,
String provisioningUserStoreId, String tenantDomain) throws FrameworkException {
roles.add("Internal/subscriber");
super.handle(roles, subject, attributes, provisioningUserStoreId, tenantDomain);
}
}
Further here are few other implementations for custom provisioning handlers
tharakawijekoon/custom-provisioning-handler
nipunthathsara/wso2-custom-provisioning-handler
Hope this helps to achieve your requirement.

Google Cloud Identity Platform (CICP) SAML Workflow Fails

Background
Using Firebase Auth and a SAML Auth Provider with the following configuration:
const config = {
apiKey: "AIzaSy...",
authDomain: "example-app.firebaseapp.com",
};
firebase.initializeApp(config);
const provider = new firebase.auth.SAMLAuthProvider('saml.example-idp');
function saml() {
firebase.auth().signInWithRedirect(provider)
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
The CICP configuration for the SAML upstream has the Service Provider: our entity id and the ACS configured as our CICP https://example-app.firebaseapp.com/__/auth/handler.
What I expect to happen
To be able to set a breakpoint in the signInWithRedirect()'s Promise's then and see the authenticated user.
What actually happens
Flow is redirected to the IdP and authentication handled.
The IdP emits the redirect-posting page with automatic submit-on-load and a multipart/form-data form with:
Content-Disposition: form-data; name=SAMLResponse - containing base64
encoded signed SAMLResponse
Content-Disposition: form-data;
name=RelayState - containing the relay state from the SAML flow
Content-Disposition: form-data; name="ssourl" - containing the
firebase project auth handler URI
This in turn causes CICP to render and return a page with script that sets up
var POST_BODY=""------WebKitFormBoundary9bn7AOpnZiIRk9qZ\r\nContent....."
i.e. rather than parsing the form body and extracting the SAMLResponse field, it is replaying the entire Request.body into the script and then calling fireauth.oauthhelper.widget.initialize();
This obviously fails because that roundtrips and then attempts to post the entire response body to the /__/auth/handler endpoint as a querystring.
I suspect there's a simple configuration item that's missing from this chain, because all of the flows look normal to me until the multipart form data gets pushed into the POST_BODY and then prevents the transform of the SAML token into an OAuth token.
The question
What configuration item is incorrect in this (redacted) setup, and what is the correct derivation of the value to replace it with?
Maybe there is also an additional technical issue with SAML, but at least there's a incoherence point in the way sign-in method is used.
According to (Official Docs)[https://cloud.google.com/identity-platform/docs/how-to-enable-application-for-saml#handle-signin-with-client-sdk], you have 2 options to sign-in :
1) With Popup
In this case, you can use a promise to retrieve user credential with sign-in method:
firebase.auth().signInWithPopup(provider)
.then((result) => {
// User is signed in.
// Identity provider data available in result.additionalUserInfo.profile,
// or from the user's ID token obtained via result.user.getIdToken()
// as an object in the firebase.sign_in_attributes custom claim
// This is also available via result.user.getIdTokenResult()
// idTokenResult.claims.firebase.sign_in_attributes.
})
.catch((error) => {
// Handle error.
});
2) With Redirect
In this case, your code should be split into 2 parts.
First sign-in method, without using any promise :
firebase.auth().signInWithRedirect(provider);
Then, the initialization of a "listener", to retrieve user credential after the sign-in redirect :
// On return.
firebase.auth().getRedirectResult()
.then((result) => {
// User is signed in.
// Identity provider data available in result.additionalUserInfo.profile,
// or from the user's ID token obtained via result.user.getIdToken()
// as an object in the firebase.sign_in_attributes custom claim
// This is also available via result.user.getIdTokenResult()
// idTokenResult.claims.firebase.sign_in_attributes.
})
.catch((error) => {
// Handle error.
});
To be added in the bootstrap part of your page/app.
Hope it will help.
Short answer to long question:
The SAML provider handling in Firebase Auth and Google CICP doesn't process multipart/form-data and needs to be in application/x-www-form-urlencoded.
This is a SAML IdP configuration, not something which can be handled by the Firebase Auth service provider configuration.

Websphere Role Based WS-Security with UsernameToken

Through the Websphere Console I've setup a Policy Set and a Policy Set Binding in order to support UsernameToken authentication on a webservice. As expected, it is rejecting web service calls without correct usernames and passwords. However, it is now accepting every user in the connected LDAP.
I would like to be able to only allow access to users in a specific LDAP group. I have the feeling that I need to create a custom JAAS Login in the Caller settings, but I'm not completely sure.
Does anybody have a solution for this, or a direction where I should be looking?
Edit: I'm doing this to expose an IBM BPM web service.
Create your web service based on EJB not a POJO, and then use #RolesAllowed annotation to specify roles which are allowed to invoke particular method from your service. Use adminconsole, scirpt or binding file to map defined role to user or groups from the LDAP server.
This is probably much easier than fighting with Login module and more flexible.
You can create a custom JAAS login module to use when consuming the username token. You can use a JAAS config that first calls the built-in token consumer, then your custom consumer. Doing it this way means that you can use the built-in consumer to parse the token and do timestamp and nonce processing and you only have to do the username/password validation in your own login module.
The instructions can be found here: http://www14.software.ibm.com/webapp/wsbroker/redirect?version=phil&product=was-nd-dist&topic=twbs_replace_authmethod_usernametoken
(Please forgive the formatting. I'm doing the best I can with what I have available here.)
Replacing the authentication method of the UsernameToken consumer using a stacked JAAS login module
By default, the Web services security UsernameToken consumer, UNTConsumeLoginModule, always validates the username and password that are contained within the token against the WebSphere registry. You can use the SPIs that GenericSecurityTokenFactory provides to bypass this authentication method.
About this task
If you want to replace the authentication method that UNTConsumeLoginModule uses, you must provide your own custom JAAS login module to do the authentication. The custom login module is stacked under UNTConsumeLoginModule in a custom JAAS configuration. The UNTConsumeLoginModule consumes and validates the token XML. The validation of the values provided for username and password is deferred to the custom stacked login module.
Because the use of UNTConsumeLoginModule carries with it the assumption that the username and password will be authenticated, more requirements are put on a stacked login module that intends to perform this function than are put on login modules that are only intended to provide dynamic token functionality.
To indicate to UNTConsumeLoginModule that it should not authenticate the username and password, you must set the following property on the configured callback handler:
com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true
Like most WS-Security login modles, UNTConsumeLoginModule always puts the consumed token in the shared state map to which all login modules in the stack have access. When authDeferred=true is specified, in the commit phase, UNTConsumeLoginModule ensures that the same UsernameToken object that had originally been put on the shared state has been put in another location in the shared state. If this UsernameToken object cannot be found, a LoginException occurs. Therefore, you cannot just set authDeferred=true on the callback handler without having an accompanying login module return the token to the shared state.
Procedure
Develop a JAAS login module to do the authentication and make it available to your application code. This new login module stacks under the com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule.
This login module must:
Use the following method to get the UsernameToken that UNTConsumeLoginModule consumes.
UsernameToken unt = UsernameToken)factory.getConsumerTokenFromSharedState(sharedState,UsernameToken.ValueType);
In this code example, factory is an instance of com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory.
Check the username and password in the manner that you choose.
You can call unt.getUsername() and unt.getPassword() to get the username and password.
Your login module should throw a LoginException if there is an authentication error.
Put the UsernameToken, that was obtained in the previous substep, back on the shared state.
Use the following method to put the UsernameToken back on the shared state.
factory.putAuthenticatedTokenToSharedState(sharedState, unt);
Following is an example login module:
package test.tokens;
import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.ibm.websphere.wssecurity.wssapi.token.UsernameToken;
import java.util.ArrayList;
import com.ibm.wsspi.security.registry.RegistryHelper;
import com.ibm.websphere.security.UserRegistry;
public class MyUntAuthenticator implements LoginModule {
private Map _sharedState;
private Map _options;
private CallbackHandler _handler;
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this._handler = callbackHandler;
this._sharedState = sharedState;
this._options = options;
}
public boolean login() throws LoginException {
//For the sake of readability, this login module does not
//protect against all NPE's
GenericSecurityTokenFactory factory = null;
WSSUtilFactory utilFactory = null;
try {
factory = GenericSecurityTokenFactory.getInstance();
utilFactory = WSSUtilFactory.getInstance();
} catch (Exception e) {
throw new LoginException(e.toString());
}
if (factory == null) {
throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
}
UsernameToken unt = (UsernameToken)factory.getConsumerTokenFromSharedState(this._sharedState,UsernameToken.ValueType);
String username = unt.getUsername();
char [] password = unt.getPassword();
//authenticate the username and password
//to validate a PasswordDigest password (fixpack 8.5.5.8 and later)
//String pw = yourCodeToLookUpPasswordForUsername(username);
//boolean match = utilFactory.verifyDigestedPassword(unt, pw.toCharArray());
//if (!match) throw new LoginException("Digested passwords do not match");
//Example:
try {
simpleUserGroupCheck(username, password, "cn=group1,o=ibm,c=us");
} catch (Exception e) {
LoginException le = new LoginException(e.getMessage());
le.initCause(e);
throw le;
}
//Put the authenticated token to the shared state
factory.putAuthenticatedTokenToSharedState(this._sharedState, unt);
return true;
}
private boolean simpleUserGroupCheck(String username, char [] password, String group) throws Exception {
String allowedGroup = null;
//get the default user registry
UserRegistry user_reg = RegistryHelper.getUserRegistry(null);
//authenticate the user against the user registry
user_reg.checkPassword(username, new String(password));
//get the list of groups that the user belongs to
java.util.List<String> groupList = user_reg.getGroupsForUser(username);
//you can either use a hard-coded group
allowedGroup = group;
//or get the value from your own custom property on the callback handler
//WSSUtilFactory util = WSSUtilFactory.getInstance();
//Map map = util.getCallbackHandlerProperties(this._handler);
//allowedGroup = (String) map.get("MY_ALLOWED_GROUP_1");
//check if the user belongs to an allowed group
if (!groupList.contains(allowedGroup)) {
throw new LoginException("user ["+username+"] is not in allowed group ["+allowedGroup+"]");
}
return true;
}
//implement the rest of the methods required by the
//LoginModule interface
}
Create a new JAAS login configuration.
In the administrative console, select Security > Global security.
Under Authentication, select Java Authentication and Authorization Service.
Select System logins.
Click New, and then specify Alias = test.consume.unt.
Click New, and then specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule
Click OK.
Click New, and then specify Module class name = test.tokens.MyUntAuthenticator
Select Use login module proxy.
Click OK, and then click SAVE.
Configure your UsernameToken token consumer to use the new JAAS configuration.
Open your bindings configuration that you want to change.
In the administrative console, select WS-Security > Authentication and protection.
Under Authentication tokens, select the UsernameToken inbound token that you want to change.
Select JAAS login = test.consume.unt.
Set the required property on the callback handler that is configured for the UsernameToken consumer.
Click Callback handler.
Add the com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true custom property.
Click OK.
Click SAVE.
Restart the application server to apply the JAAS configuration changes.
Test your service.

ASP.NET Identity / OData using CORS and cookie authentication missing Auth cookie

I have an ASP.NET Identity site and a ASP.NET OData site.
Both sites have CORS enabled and both site are using ASP.NET Identity CookieAuthentication.
When I execute both sites locally on my computer using IIS (not express) the AUTH cookie is being passed in the header on each request to the OData site.
But when I deploy the sites to the production IIS server then the header is missing the AUTH cookie when calling the production OData site.
Both production and my local IIS have the same domain name and CORS is setup to allow all.
The WebApiConfig has
cors = new Http.Cors.EnableCorsAttribute("*", "*", "*");
config.Enable(cors);
Before anyone asks, yes the machine key is the same between sites.
UPDATE
This seems to be a CORS issue.
When both sites are on my local machine they use the same host name and domain name but when the site are on the production server they have different host names and the same domain name.
You might need to specify the "Access-Control-Allow-Origin" inside your OAuthAuthorizationServerProvider.
I'm using OWIN but you should be able to do something similarly.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try adding the policy in your OWIN startup class as below. Just keep in mind that the Startup class might have some different class files since it's a partial class. Also, check ConfigureAuth method to see if everything is set according to your needs there. For instance, you set the external signin cookie of Identity as copied below in ConfigureAuth method to allow External Signin Cookeies like facebook and google.
public void Configuration(IAppBuilder app)
{
//
app.UseCors(CorsOptions.AllowAll);
ConfigureAuth(app);
}
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
I finally got this to work.
In the ASP.NET Identity site I have the following:
// configure OAuth for login
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider(),
LoginPath = new PathString("/Account/Login.aspx"),
CookieName = ".TESTAUTH",
CookieDomain = ".test.com",
CookieSecure = CookieSecureOption.Always
});
It seems that the important part on the ASP.NET Identity site is that the "CookieName, CookieDomain, and the Machine Key" must match the one on the OData site.
And then on the OData site I have the following:
// configure OAuth for login
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider { OnApplyRedirect = ApplyRedirect },
LoginPath = new PathString("/Account/Login.aspx"),
CookieName = ".TESTAUTH",
CookieDomain = ".test.com",
CookieSecure = CookieSecureOption.Always
});
// build the configuration for web api
HttpConfiguration config = new HttpConfiguration();
// Enable CORS (Cross-Origin Resource Sharing) for JavaScript / AJAX calls
// NOTE: USING ALL "*" IS NOT RECOMMENDED
var cors = new Http.Cors.EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
// call the web api startup
WebApiConfig.Register(config);
app.UseWebApi(config);
private void ApplyRedirect(CookieApplyRedirectContext context)
{
Uri absoluteUri = null;
if (Uri.TryCreate(context.RedirectUri, UriKind.Absolute, absoluteUri))
{
var path = PathString.FromUriComponent(absoluteUri);
if (path == context.OwinContext.Request.PathBase + context.Options.LoginPath)
{
QueryString returnURI = new QueryString(context.Options.ReturnUrlParameter, context.Request.Uri.AbsoluteUri);
context.RedirectUri = "https://www.test.com/Account/Login.aspx" + returnURI.ToString;
}
}
context.Response.Redirect(context.RedirectUri);
}
The "LoginPath" is required even though it does not exist on the OData site and you can't use a full url to another site for the login path.
I used "OnApplyRedirect" to redirect to the actual Login page.
I'm not sure what the difference is between "config.EnableCors" and "app.UseCors" but the EnableCors seems to be working for now.