how to do a global OIDC logout using node-oidc-provider - node.js

What is the correct way in OIDC for an RP to initiate a global logout of all services to which the user is logged in via the OP? I can logout of a single service, but I've read you can create a frontend url for each RP, and load that as an iframe in the OP logout form, which seems somewhat flaky and cumbersome. I've also read up on backchannel logouts, but the library I'm using doesn't seem to support a global logout via this method.
The best thing I can think to do is override the configuration.features.rpInitiatedLogout.logoutSource function and implement what is defined in endSession
if (session.authorizations) {
await Promise.all(
Object.entries(session.authorizations).map(async ([clientId, { grantId }]) => {
// Drop the grants without offline_access
// Note: tokens that don't get dropped due to offline_access having being added
// later will still not work, as such they will be orphaned until their TTL hits
if (grantId && !session.authorizationFor(clientId).persistsLogout) {
await revoke(ctx, grantId);
}
}),
);
}
I'd love to know if there is a better, more idiomatic solution.

It turns out that the global logout is the default behavior, assuming that none of the RP's authorizations have persistsLogout set to true. This can happen if they include the offline_access scope or if the provider.configuration.expiresWithSession is overridden to return false, which was the case in my situation.

Related

How can I protect the loopback explorer by username and password?

I've just started using loopback4 and I would like to protect the /explorer from being public. The user would initially see a page where username and password must be entered. If successful, the user is redirected to /explorer where he can see all API methods (and execute them). If user is not authenticated, accessing the path /explorer would give a response of "Unauthorized". Is there a way to easily implement this?
There is issue talking about a GLOBAL default strategy is enabled for all routes including explorer in https://github.com/strongloop/loopback-next/issues/5758
The way is to specify a global metadata through the options:
this.configure(AuthenticationBindings.COMPONENT).to({
defaultMetadata: {
strategy: 'JWTStrategy'
}
})
this.component(AuthenticationComponent);
registerAuthenticationStrategy(this, JWTAuthenticationStrategy)
But in terms of enabling a single endpoint added by route.get(), it's not supported yet, see code of how explorer is registered. #loopback/authentication retrieves auth strategy name from a controller class or its members, but if the route is not defined in the controller, it can only fall back to the default options, see implementation

MSAL Scopes (openid profile offline_access). Basic Simple Profiles May Not be Possible?

MSAL behaves as though there is a hard-coded catch 22 at its API library layer that seems illogical when I use it.
string[] scopesArrayNonNullWORKS = new string[] { "email" };
string[] scopesArrayAlreadyThereInMsalCalls_FAILS = new string[] { "openid" };
string[] scopesArrayNoExtraScopesNeeded_FAILS = new string[0];
Microsoft.Identity.Client.ConfidentialClientApplication myCliApp;
myCliApp.AcquireTokenByAuthorizationCodeAsync(code, scopesArray);
MSAL has built in and hard coded these scopes on every call: openid , profile , offline_access .
This is fine and works for me. I have no need for any additional scopes.
However, I can’t use null or an empty scopes list. It is like the MSAL library layer is forcing me to ask for scopes I do not need or want. If I include email (which I don’t’ need) then the library layer is happy with a non-null Scopes parameter and everything works.
If I use the one scope I need, openid, then the library layer errors because I have included a duplicate scope openid which was already there.
This seems like a catch 22 and cyclically illogical. I can’t use the scopes I need, or, it errors because they are predefined. I can’t pass in an empty list of scopes (and use the pre-defined) or it errors. If I pass in a non-null scope that I do not want or need then it works.
I must be missing a critical conceptual detail.
I would like to use these 3 and only these 3 scopes ... openid , profile , offline_access .
An Error Example of this catch 22: MSAL always sends the scopes 'openid profile offline_access'. They cannot be suppressed as they are required for the library to function. Do not include any of these scopes in the scope parameter.
The question wasn't really formulated as a question, but if your question is really "is it possible to authenticate to a application which doesn't require additional scopes", then I found a workaround which is definitely a hack and may not work forever. I couldn't throw in any placeholder scopes to make the client API happy, because the server rejected them. But sending in a blank space made the API shut up and did not seem to affect the application at all.
string[] scopes = new[] {" "};

OnValidateIdentity disables the MVC OWIN remember me option

When I activate the OWIN logout-everywhere feature via security stamps and use the OnValidateIdentity-Callback of the CookieAuthenticationProvider with the SecurityStampValidator-class, the user is logged out every time he closes the browser.
provider.OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<MyUserManager, MyUser>(
System.TimeSpan.FromSeconds(10),(manager, user) => {
return user.GenerateUserIdentityAsync(manager);
});
However, when I do the plumbing myself (lookup and comparison of the security stamps, rejecting or renewing the identity) in the OnValidateIdentity-callback, everything seems to work fine.
Is this a known bug, or do I miss here something? Or is there a good documentation about the CookieAuthenticationProvider and the use of OnValidateIdentity?
Digging with google only shows me some simple samples, but gives no further insight.
Additional information
I use an own implementation of the UserStorage which saves all the
data in a database
I noted that every page request calls two times the
GetSecurityStampAsync of the UserStorage, wheras when I use my
implementation, only one call is done.
Installed Identity Version is 2.0.1
This is basically a bug, the regeneration of the cookie should respect the current Remember Me option on the cookie. As a workaround, you can copy the OnValidateIdentity code and feed in the current context properties to flow the Persistent mode through:
context.OwinContext.Authentication.SignIn(context.Properties, identity);
This is resolved in ASP.NET Identity 2.2. See https://aspnetidentity.codeplex.com/workitem/2319
I have found the following code in the disassembly of SecurityStampValidator.OnValidateIdentity:
// .. some other code
// ...
ClaimsIdentity claimsIdentity = await regenerateIdentityCallback(userManager, tUser);
if (claimsIdentity != null){
context.get_OwinContext().get_Authentication().SignIn(new ClaimsIdentity[]
{
claimsIdentity
});
}
It seems to me, that the SignIn-operation is incomplete and should set the remember-me option? Therefore I assume that the implementation of SecurityStampValidator is buggy.

Logout on ServiceStack v4

I have ServiceStack v4 service but when I call the auth/logout route (using either POST or GET) to logout the currently logged-in user, I get an error:
400 Not Empty
User Name cannot be empty
Password Cannot be empty
As I wouldn't expect users to enter credentials when logging out, I am surely missing something?
I have the AuthFeature registered during host initialisation, and I am using CredentialsAuthProvider. I have taken the code from Github so I can see how it works.
My Client Code:
var rest = Restangular.one('auth/logout').get();
//var result = rest.post({userName: userName});
this.requestTracker.addPromise(rest);
return rest;
After a lot of digging, this happens when you are using CredentialsAuthProvider. Within this class, a validator is defined that validates all instances of the Authenticate request. As the logout route uses the Authenticate request, this validator is fired.
I got round it by modifying the validator to:
RuleFor(x => x.UserName).NotEmpty().When(d => d.provider != "logout");
RuleFor(x => x.Password).NotEmpty().When(d => d.provider != "logout");
This is probably not the most elegant way of fixing long term, but got me up and running.
I know this question is old, but I recently have been struggling with the same thing. What occurs is that before the Authenticate.Post function is called, the validation cache is checked and the CredentialsAuthProvider which has the mentioned validator fails unless username and password are not empty.
Now, i'm not sure if it makes a difference if you only have that provider enabled or not - I've not tested. I actually have my own custom provider that subclasses CredentialsAuthProvider and it's the only one I register.
The only way currently is to either pass a non-empty (but useless) password and username, or modify your own custom provider, overriding the Authenticate function and using a modified version of the validator as mentioned above.

Servicestack - Call AuthProvider automatically

I would like to build my own AuthProvider. It should
Check if ss-id cookie is set and check for a valid session (this is
done automatically in servicestack)
If no valid session was found check a custom http-header (e.g. X-Api-Token)
If found a valid token create a new session
If not found a valid token send 401 Unauthorized
Basically this is the behaviour of the CredentialsAuthProvider except that I need to check for the X-Api-Token without making an explicit call to /auth/credentials. However the AuthProvider is never called automatically.
Any ideas how to get this done?
Edit: One idea was to use a request filter but there is still something missing:
this.GlobalRequestFilters.Add((request, response, arg3) =>
{
//If there is a valid ss-id cookie the it should have precedence and the request should be authenticated accordingly
if (!ValidatedViaSsIdCookie())
{
if (HeaderHasCorrectApiKey()) {
//Authenticate the current request by creating a new Session
AuthenticateRequest();
}
}
}
);
How to implement ValidatedViaSsIdCookie() and AuthenticateRequest()???
Edit: I don't think GlobalRequestFilters are the way to go because they will be executed after authentication... So if there is no valid session the filter is not executed at all and my Api key is never checked... Still searching for a better solution...
Regards
Dirk

Resources