CookieTheftException with PersistentTokenBasedRememberMeServices - grails

I'm using the PersistentTokenBasedRememberMeServices (Spring Security 2.04) in Grails App in conjunction with the OpenIDAuthenticationProcessingFilter. The configuration is as follows (This is Grails's DSL equivalent to Spring resource.xml but it should be quite easy to adapt):
customTokenRepository(JdbcTokenRepositoryImpl)
{
dataSource = ref('dataSource')
}
rememberMeServices(PersistentTokenBasedRememberMeServices) {
userDetailsService = ref('userDetailsService')
key = securityConf.rememberMeKey
cookieName = securityConf.cookieName
alwaysRemember = securityConf.alwaysRemember
tokenValiditySeconds = securityConf.tokenValiditySeconds
parameter = securityConf.parameter
tokenRepository = customTokenRepository
}
openIDAuthProvider(org.codehaus.groovy.grails.plugins.springsecurity.openid.GrailsOpenIdAuthenticationProvider) {
userDetailsService = ref('userDetailsService')
}
openIDStore(org.openid4java.consumer.InMemoryConsumerAssociationStore)
openIDNonceVerifier(org.openid4java.consumer.InMemoryNonceVerifier, securityConf.openIdNonceMaxSeconds) // 300 seconds
openIDConsumerManager(org.openid4java.consumer.ConsumerManager) {
nonceVerifier = openIDNonceVerifier
}
openIDConsumer(org.springframework.security.ui.openid.consumers.OpenID4JavaConsumer, openIDConsumerManager)
openIDAuthenticationProcessingFilter(org.springframework.security.ui.openid.OpenIDAuthenticationProcessingFilter) {
authenticationManager = ref('authenticationManager')
authenticationFailureUrl = securityConf.authenticationFailureUrl //'/login/authfail?login_error=1' // /spring_security_login?login_error
defaultTargetUrl = securityConf.defaultTargetUrl // '/'
filterProcessesUrl = '/j_spring_openid_security_check' // not configurable
rememberMeServices = ref('rememberMeServices')
consumer = openIDConsumer
targetUrlResolver = customTargetUrlResolver
}
After a user has authenticated everything is fine until the cookie issued to him is used for the first time for example after a container restart (see here).
The very first request using the cookie seems to be always fine but after the cookie has been updated with a new date and most importantly a new token, subsequent requests will crash in here. As if the browser would still request resources using the old version of the cookie containing the old token. I'm totally baffled why this happens. Any suggestions?

Related

Difference between oauth2/v2 vs oidc Spotify API

i'm currently trying to connect via UNO-Plattform sample to the Spotify API.
https://github.com/unoplatform/Uno.Samples/blob/master/UI/Authentication.OidcDemo/Authentication.OidcDemo/Authentication.OidcDemo.Shared/MainPage.xaml.cs
Therefore I have updated the PrepareClient method.
private async void PrepareClient()
{
var redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().OriginalString;
// Create options for endpoint discovery
var options = new OidcClientOptions
{
Authority = "https://accounts.spotify.com", //"https://demo.duendesoftware.com/",
ClientId = "7c1....a45",
ClientSecret = "4b..a",
Scope = "playlist-read-private",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode
};
// Create the client. In production application, this is often created and stored
// directly in the Application class.
_oidcClient = new OidcClient(options);
var extra_parameters = new Dictionary<string, string>();
//extra_parameters.Add("response_type", "token"); // if i add this line i get an error
_loginState = await _oidcClient.PrepareLoginAsync(extra_parameters);
btnSignin.IsEnabled = true;
// Same for logout url.
//If i add this line a get an error
//_logoutUrl = new Uri(await _oidcClient.PrepareLogoutAsync(new LogoutRequest()));
btnSignout.IsEnabled = true;
}
private async void SignIn_Clicked(object sender, RoutedEventArgs e)
{
var startUri = new Uri(_loginState.StartUrl);
// Important: there should be NO await before calling .AuthenticateAsync() - at least
// on WebAssembly, in order to prevent triggering the popup blocker mechanisms.
var userResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, startUri);
if (userResult.ResponseStatus != WebAuthenticationStatus.Success)
{
txtAuthResult.Text = "Canceled";
// Error or user cancellation
return;
}
// User authentication process completed successfully.
// Now we need to get authorization tokens from the response
var authenticationResult = await _oidcClient.ProcessResponseAsync(userResult.ResponseData, _loginState);
if (authenticationResult.IsError)
{
var errorMessage = authenticationResult.Error;
// TODO: do something with error message
txtAuthResult.Text = $"Error {errorMessage}";
return;
}
// That's completed. Here you have to token, ready to do something
var token = authenticationResult.AccessToken;
var refreshToken = authenticationResult.RefreshToken;
// TODO: make something useful with the tokens
txtAuthResult.Text = $"Success, token is {token}";
}
If i use Postman for authentication, i can use the URL
curl --location --request GET 'https://accounts.spotify.com/authorize?response_type=token&client_id=7c...45&scope=playlist-read-private&redirect_uri=http://localhost:8080&state=test'
and everything works fine and i get the token in the callback url as parameter.
If i add as "extra_parameters" the "response_type" : "token" i get the message, that this parameter is not supported...
I'm a little bit stucked here and don't know how to proceed.
I'm happy about any help in every direction to get this autentication done with uno-plattform.
OIDC can be described as a superset of OAuth2. It is a way for an identity provider to issue tokens and supply info about a user via additional APIs. Read more here.
The Oidc code that you use (probably IdentityModel.OidcClient?) requires a the service you’re calling to implement a few extra endpoints which Spotify has not implemented for their API. This is discussed in this forum topic. Because of the missing Oidc support, your code will try making calls that do not work.
The SpotifyAPI-NET library might also help you authenticate and make API calls instead.

itfoxtec-identity-saml2 SAML Request Destination Port being stripped out

On making a SAML request the port number (443) is being stripped out of the Destination. I understand this is default behaviour of the URI object. However the SAML identity provider requires the destination includes the port number for validation.
How can I get the SAML builder to include the port? 443 is being stripped from https://sit-api.eat.xxxxxx.xxxx.xx:443/samlsso (see below)
Saml2Configuration samlconfig = GetSAMLConfig();
var samlRequest = new Saml2AuthnRequest(samlconfig);
samlRequest.AssertionConsumerServiceUrl = new Uri(_appConfiguration["Saml2:AssertionConsumerServiceUrl"]);
samlRequest.Destination = new Uri(_appConfiguration["Saml2:SingleSignOnDestination"]); // https://sit-api.eat.xxxxxx.xxxx.xx:443/samlsso
samlRequest.NameIdPolicy = new NameIdPolicy()
{
AllowCreate = false,
Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
SPNameQualifier = _appConfiguration["Saml2:SPNameQualifier"]
};
samlRequest.Conditions = new Condition();
samlRequest.Conditions.Items = new List<ITfoxtec.Identity.Saml2.Schemas.Conditions.ICondition>();
samlRequest.Conditions.Items.Add(new ITfoxtec.Identity.Saml2.Schemas.Conditions.AudienceRestriction() { Audiences = new List<Audience>() { new Audience() { Uri = _appConfiguration["Saml2:AllowedAudienceUris"] } } });
var bnd = binding.Bind(samlRequest);
It is possible to change the destination URL after the ToActionResult method has been called if you are using a Saml2RedirectBinding. And thereby overriding the default behavior.
Like this:
var action = binding.ToActionResult() as RedirectResult;
action.Url = action.Url.Replace("https://sit-api.eat.xxxxxx.xxxx.xx/samlsso", "https://sit-api.eat.xxxxxx.xxxx.xx:443/samlsso");
return action;

Invalid Access Token/Missing Claims when logged into IdentityServer4

I have a standard .NET Core 2.1 (MVC and API) and Identity Server 4 project setup.
I am using reference tokens instead of jwt tokens.
The scenario is as follows:
Browse to my application
Redirected to Identity Server
Enter valid valid credentials
Redirected back to application with all claims (roles) and correct access to the application and API
Wait an undetermined amount of time (I think it's an hour, I don't have the exact timing)
Browse to my application
Redirected to Identity Server
I'm still logged into the IDP so I'm redirected immediately back to my
application
At this point the logged in .NET user is missing claims (roles) and no longer has access to the API
The same result happens if I delete all application cookies
It seems obvious to me that the access token has expired. How do I handle this scenario? I'm still logged into the IDP and the middleware automatically logged me into my application, however, with an expired (?) access token and missing claims.
Does this have anything to do with the use of reference tokens?
I'm digging through a huge mess of threads and articles, any guidance and/or solution to this scenario?
EDIT: It appears my access token is valid. I have narrowed my issue down to the missing user profile data. Specifically, the role claim.
When I clear both my application and IDP cookies, everything works fine. However, after "x" (1 hour?) time period, when I attempt to refresh or access the application I am redirected to the IDP then right back to the application.
At that point I have a valid and authenticated user, however, I am missing all my role claims.
How can I configure the AddOpenIdConnect Middleware to fetch the missing claims in this scenario?
I suppose in the OnUserInformationReceived event I can check for the missing "role" claim, if missing then call the UserInfoEndpoint...that seems like a very odd workflow. Especially since on a "fresh" login the "role" claim comes back fine. (Note: I do see the role claim missing from the context in the error scenario).
Here is my client application configuration:
services.AddAuthentication(authOpts =>
{
authOpts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authOpts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts => { })
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, openIdOpts =>
{
openIdOpts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
openIdOpts.Authority = settings.IDP.Authority;
openIdOpts.ClientId = settings.IDP.ClientId;
openIdOpts.ClientSecret = settings.IDP.ClientSecret;
openIdOpts.ResponseType = settings.IDP.ResponseType;
openIdOpts.GetClaimsFromUserInfoEndpoint = true;
openIdOpts.RequireHttpsMetadata = false;
openIdOpts.SaveTokens = true;
openIdOpts.ResponseMode = "form_post";
openIdOpts.Scope.Clear();
settings.IDP.Scope.ForEach(s => openIdOpts.Scope.Add(s));
// https://leastprivilege.com/2017/11/15/missing-claims-in-the-asp-net-core-2-openid-connect-handler/
// https://github.com/aspnet/Security/issues/1449
// https://github.com/IdentityServer/IdentityServer4/issues/1786
// Add Claim Mappings
openIdOpts.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); /* SID alias */
openIdOpts.ClaimActions.MapJsonKey("role", "role", "role");
openIdOpts.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = settings.IDP.ClientId,
ValidIssuer = settings.IDP.Authority,
NameClaimType = "name",
RoleClaimType = "role"
};
openIdOpts.Events = new OpenIdConnectEvents
{
OnUserInformationReceived = context =>
{
Log.Info("Recieved user info from IDP.");
// check for missing roles? they are here on a fresh login but missing
// after x amount of time (1 hour?)
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
Log.Info("Redirecting to identity provider.");
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Log.Debug("OnTokenValidated");
// this addressed the scenario where the Identity Server validates a user however that user does not
// exist in the currently configured source system.
// Can happen if there is a configuration mismatch between the local SID system and the IDP Client
var validUser = false;
int uid = 0;
var identity = context.Principal?.Identity as ClaimsIdentity;
if (identity != null)
{
var sub = identity.Claims.FirstOrDefault(c => c.Type == "sub");
Log.Debug($" Validating sub '{sub.Value}'");
if (sub != null && !string.IsNullOrWhiteSpace(sub.Value))
{
if (Int32.TryParse(sub.Value, out uid))
{
using (var configSvc = ApiServiceHelper.GetAdminService(settings))
{
try
{
var usr = configSvc.EaiUser.GetByID(uid);
if (usr != null && usr.ID.GetValueOrDefault(0) > 0)
validUser = true;
}
catch { }
}
}
}
Log.Debug($" Validated sub '{sub.Value}'");
}
if (!validUser)
{
// uhhh, does this work? Logout?
// TODO: test!
Log.Warn($"Unable to validate user is SID for ({uid}). Redirecting to '/Home/Logout'");
context.Response.Redirect("/Home/Logout?msg=User not validated in source system");
context.HandleResponse();
}
return Task.CompletedTask;
},
OnTicketReceived = context =>
{
// TODO: Is this necessary?
// added the below code because I thought my application access_token was expired
// however it turns out I'm actually misisng the role claims when I come back to the
// application from the IDP after about an hour
if (context.Properties != null &&
context.Properties.Items != null)
{
DateTime expiresAt = System.DateTime.MinValue;
foreach (var p in context.Properties.Items)
{
if (p.Key == ".Token.expires_at")
{
DateTime.TryParse(p.Value, null, DateTimeStyles.AdjustToUniversal, out expiresAt);
break;
}
}
if (expiresAt != DateTime.MinValue &&
expiresAt != DateTime.MaxValue)
{
// I did this to synch the .NET cookie timeout with the IDP access token timeout?
// This somewhat concerns me becuase I thought that part should be done auto-magically already
// I mean, refresh token?
context.Properties.IsPersistent = true;
context.Properties.ExpiresUtc = expiresAt;
}
}
return Task.CompletedTask;
}
};
});
I'm sorry folks, looks like I found the source of my issue.
Total fail on my side :(.
I had a bug in the ProfileService in my Identity Server implementation that was causing the roles to not be returned in all cases
humph, thanks!

IDX10503: Signature validation failed after updating to Owin.Security v 4.0.0

As per subject, I updated the Owin.Security.WsFederation and dependent packages to version 4.0 and I get the error.
I did not make any code changes other than changing
using Microsoft.IdentityModel.Protocols;
to
using Microsoft.IdentityModel.Protocols.WsFederation;
where is the WsFederationConfiguration class seems to be now.
Here is my StartupAuth:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
// Create WsFed configuration from web.config wsfed: values
var wsconfig = new WsFederationConfiguration()
{
Issuer = ConfigurationManager.AppSettings["wsfed:Issuer"],
TokenEndpoint = ConfigurationManager.AppSettings["wsfed:TokenEndPoint"],
};
/*
* Add x509 certificates to configuration
*
*/
// certificate.1 must always exist
byte[] x509Certificate;
x509Certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["wsfed:certificate.1"]);
wsconfig.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(x509Certificate)));
// certificate 2 may exist
if (ConfigurationManager.AppSettings["wsfed:certificate.2"] != null)
{
x509Certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["wsfed:certificate.2"]);
wsconfig.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(x509Certificate)));
}
// certificate 3 may exist
if (ConfigurationManager.AppSettings["wsfed:certificate.3"] != null)
{
x509Certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["wsfed:certificate.3"]);
wsconfig.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(x509Certificate)));
}
// Apply configuration to wsfed Auth Options
var wsoptions = new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Configuration = wsconfig,
Wreply = ConfigurationManager.AppSettings["wsfed:Wreply"],
Wtrealm = ConfigurationManager.AppSettings["wsfed:Wtrealm"],
};
wsoptions.TokenValidationParameters.NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn";
// Add WdFederation middleware to Owin pipeline
app.UseWsFederationAuthentication(wsoptions);
}
Is there something else 4.0 needs to validate the signature? I assume it's talking about the signature of the token from the issuer. I didn't see how to enable ShowPII to see what key it's looking at.
I am using MVC5 with the full framework. Not core.
Update:
I tried to modify the code to use the metadata provided by the identity provider in a properties file to create the WsFederationConfiguration and I still get the same error. I'm not sure what the Signature is, or where I get it from if it's not in the idp metadata.
Update2:
Here are the changes I made to use the wsfed metadata provided by the sts in a properties file. (I have removed the actual base64 encoded metadata, but needless to say it is the same XML you get when you regest the metadata from an STS that publishes it as and endpoint. As I said above, I get the same error:
public void ConfigureAuth(IAppBuilder app)
{
WsFederationConfiguration wsconfig;
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
var metaDataDocument = System.Text.Encoding.UTF8.GetString(
Convert.FromBase64String("...c2NyaXB0b3I+"));
using (var metaDataReader = XmlReader.Create(new StringReader(metaDataDocument), SafeSettings))
{
wsconfig = (new WsFederationMetadataSerializer()).ReadMetadata(metaDataReader);
}
// Apply configuration to wsfed Auth Options
var wsoptions = new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Configuration = wsconfig,
Wreply = ConfigurationManager.AppSettings["wsfed:Wreply"],
Wtrealm = ConfigurationManager.AppSettings["wsfed:Wtrealm"],
};
wsoptions.TokenValidationParameters.NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn";
// Add WdFederation middleware to Owin pipeline
app.UseWsFederationAuthentication(wsoptions);
}
I worked with some folks on the team at MS. The issue here was that our STS is using SHA1 to sign the token and the new version of weFederation doesn't support SHA1 as it is not-secure and is deprecated.
The easiest way to use WIF with owin is through the usage of the federation meta data (which lives at FederationMetadata/2007-06/FederationMetadata.xml). Then you don't need to setup anything at all which is explained in Configure claims based web applications using OWIN WsFederation middleware . The precondition is of course that your STS publishes a meaningful FederationMetaData document. The nice advantage is that your public keys needed for validation are automatically picked up by your application (and renewing them is done seamlessly).
This is IMHO that is much easier than the approach you are taking.
You can follow Manual configuration of OWIN WS-Federation Identity provider as it describes a more easy way than yours.

Web API OAUTH - Distinguish between if Identify Token Expired or UnAuthorized

Am currently developing an Authorization server using Owin, Oauth, Claims.
Below is my Oauth Configuration and i have 2 questions
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(1000),
Provider = new AuthorizationServerProvider()
//RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
If the token is expired and user accessing using the expired token user is getting 401(unAuthorized).Checking using Fiddler.
How can i send a customized message to an user stating your token as expired. Which function or module i need to override.
and my another quesiton is What is the use of the below line ?
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Do i really need this to implement because when i checked it still works without the above line. Any security violation ?
You can't directly customize the behavior for expired tokens but you can do that with a custom middleware.
First override the AuthenticationTokenProvider so that you can intercept the authentication ticket before it is discarded as expired.
public class CustomAuthenticationTokenProvider : AuthenticationTokenProvider
{
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
if (context.Ticket != null &&
context.Ticket.Properties.ExpiresUtc.HasValue &&
context.Ticket.Properties.ExpiresUtc.Value.LocalDateTime < DateTime.Now)
{
//store the expiration in the owin context so that we can read it later a middleware
context.OwinContext.Set("custom.ExpriredToken", true);
}
}
}
and configure it in the Startup along with a small custom middleware
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AccessTokenProvider = new CustomAuthenticationTokenProvider()
});
//after the request has been authenticated or not
//check for our custom env setting and act accordingly
app.Use(new Func<AppFunc, AppFunc>(next => (env) =>
{
var ctx = new OwinContext(env);
if (ctx.Get<bool>("custom.ExpriredToken"))
{
//do wathever you want with the response
ctx.Response.StatusCode = 401;
ctx.Response.ReasonPhrase = "Token exprired";
//terminate the request with this middleware
return Task.FromResult(0);
}
else
{
//proceed with the rest of the middleware pipeline
return next(env);
}
}));
If you have noticed I've placed the custom middleware after the call to UseOAuthBearerAuthentication and this is important and stems from the answer to your second question.
The OAuthBearerAuthenticationMidlleware is responsible for the authentication but not for the authorization. So it just reads the token and fills in the information so that it can be accessed with IAuthenticationManager later in the pipeline.
So yes, with or without it all your request will come out as 401(unauthorized), even those with valid tokens.

Resources