How do I force AWS Cognito to retrieve an IdentityId from the server? - amazon-cognito

In the iOS SDK (v2.4.8) I can't logout a user and then login as a different user correctly.
The (correct) cognityIdentityId returned by AWS for the first user (since an app start) is also returned for the second user (unless the app is restarted). This gives access to the AWSCognitoDataset of one user by another.
I think this is because the iOS SDK has cached the id and the documented call to clear that cache, doesn't fully work.
When logging in:
// one-off initialisation
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType:AWSRegionType.USEast1, identityPoolId:Constants.CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(region:AWSRegionType.USEast1, credentialsProvider:self.credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
…
// I get idToken from my external provider serice (Auth0)
func doAmazonLogin(idToken: String, success : () -> (), _ failure : (NSError) -> ()) {
var task: AWSTask?
//Initialize clients for new idToken
if self.credentialsProvider?.identityProvider.identityProviderManager == nil || idToken != Application.sharedInstance.retrieveIdToken() {
let logins = [Constants.CognitoIDPUrl: idToken]
task = self.initializeClients(logins)
} else {
//Use existing clients
self.credentialsProvider?.invalidateCachedTemporaryCredentials()
task = self.credentialsProvider?.getIdentityId()
}
//Make login
task!.continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
failure(task.error!)
} else {
// the task result will contain the identity id
let cognitoId:String? = task.result as? String
self.customIdentityProviderManager!.addToken(Constants.CognitoIDPUrl, value:idToken)
//Store Cognito token in keychain
Application.sharedInstance.storeCognitoToken(cognitoId)
success()
}
return nil
}
}
func initializeClients(logins: [NSObject:AnyObject]?) -> AWSTask? {
//Create identity provider managet with logins
let manager = CustomIdentityProviderManager(tokens: logins!)
self.credentialsProvider?.setIdentityProviderManagerOnce(manager)
return self.credentialsProvider?.getIdentityId()
}
When logging out:
// Clear ALL saved values for this provider (identityId, credentials, logins). [docs][1]
let keychain = A0SimpleKeychain(service:"…")
keychain.clearAll()
I've also tried adding:
credentialsProvider!.clearCredentials()
credentialsProvider!.clearKeychain()
Is anyone using the AWS iOS SDK and has coded logout successfully such that a new user can login cleanly?
There is the oddly named method credentialsProvider.setIdentityProviderManagerOnce() - I can't find this documented but its name suggests that it should only be called once per session. But if keychain.clearAll() removes logins then one would need to call setIdentityProviderManagerOnce each time a new user logins in in order to setup logins each time.

Can you describe your login/logout flow a bit more? Cognito doesn't support multiple logins from the same provider per identity, so it sounds like, unless you're using multiple, it isn't actually changing the identity.
In any case, have you tried the clearKeyChain method on the credentials provider? It's largely for use cases like this - clearing everything.

Related

How to extend and validate session in ASP.NET Core Identity?

We want to offer the users to manage their login sessions.
This worked so far pretty easy with ASP.NET Core and WITHOUT the Identity Extensions.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes
But how can we invoke this validation with ASP.NET Core Identity?
Problem we have:
How do we store login-session-based information like Browser Version, Device Type and User Position? Do we extend any type or what is the idea?
How do we dynamically set the cookie expiration based on a specific user?
How do we invalidate the Cookie from the backend (like the link above shows)?
How do we required additional password-prompts for special functions?
It feels the ASP.NET Core Identity is still not that extensible and flexible :(
Unfortunately, this area of ASP.NET Identity is not very well documented, which I personally see as a risk for such a sensitive area.
After I've been more involved with the source code, the solution seems to be to use the SignIn process of the SignIn Manager.
The basic problem is that it's not that easy to get your custom claims into the ClaimsIdentity of the cookie. There is no method for that.
The values for this must under no circumstances be stored in the claims of the user in the database, as otherwise every login receives these claims - would be bad.
So I created my own method, which first searches for the user in the database and then uses the existing methods of the SignInManager.
After having a ClaimsIdentity created by the SignIn Manager, you can enrich the Identity with your own claims.
For this I save the login session with a Guid in the database and carry the id as a claim in the cookie.
public async Task<SignInResult> SignInUserAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
DateTimeOffset createdLoginOn = DateTimeOffset.UtcNow;
DateTimeOffset validTo = createdLoginOn.AddSeconds(_userAuthOptions.ExpireTimeSeconds);
// search for user
var user = await _userManager.FindByNameAsync(userName);
if (user is null) { return SignInResult.Failed; }
// CheckPasswordSignInAsync checks if user is allowed to sign in and if user is locked
// also it checks and counts the failed login attempts
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
if (attempt.Succeeded)
{
// TODO: Check 2FA here
// create a unique login entry in the backend
string browserAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
Guid loginId = await _eventDispatcher.Send(new AddUserLoginCommand(user.Id, user.UserName, createdLoginOn, validTo, browserAgent));
// Write the login id in the login claim, so we identify the login context
Claim[] customClaims = { new Claim(CustomUserClaims.UserLoginSessionId, loginId.ToString()) };
// Signin User
await SignInWithClaimsAsync(user, isPersistent, customClaims);
return SignInResult.Success;
}
return attempt;
}
With each request I can validate the ClaimsIdentity and search for the login id.
public class CookieSessionValidationHandler : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
ClaimsPrincipal userPrincipal = context.Principal;
if (!userPrincipal.TryGetUserSessionInfo(out int userId, out Guid sessionId))
{
// session format seems to be invalid
context.RejectPrincipal();
}
else
{
IEventDispatcher eventDispatcher = context.HttpContext.RequestServices.GetRequiredService<IEventDispatcher>();
bool succeeded = await eventDispatcher.Send(new UserLoginUpdateLoginSessionCommand(userId, sessionId));
if (!succeeded)
{
// session expired or was killed
context.RejectPrincipal();
}
}
}
}
See also
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes

Why do AWSCognitoIdentityProvider.getAdminUser and AWSCognitoIdentityProvider.listUsers return task.result that is nil?

AWS Cognito is integrated into my Swift app and logins work. However, when I try to use the AWSCognitoIdentityProvider api, the returned task.result is nil. Why? Here is the code:
let pool = AWSCognitoIdentityUserPool.default()
let poolId = pool.userPoolConfiguration.poolId
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId:poolId)
let configuration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let aguReq = AWSCognitoIdentityProviderAdminGetUserRequest()
aguReq?.userPoolId = poolId
aguReq?.username = "USERNAME_GOES_HERE"
let luReq = AWSCognitoIdentityProviderListUsersRequest()
luReq?.userPoolId = poolId
let acpDefault = AWSCognitoIdentityProvider.default()
acpDefault.adminGetUser(aguReq!).continueWith(block: { (task: AWSTask<AWSCognitoIdentityProviderAdminGetUserResponse>) -> Any? in
task.result?.userAttributes?.forEach({ (attr: AWSCognitoIdentityProviderAttributeType) in
let attrName = attr.name
let attrValue = attr.value
print("attrName=\(String(describing: attrName)); attrValue=\(String(describing: attrValue))")
})
return nil
})
acpDefault.listUsers(luReq!).continueWith(block: { (task: AWSTask<AWSCognitoIdentityProviderListUsersResponse>) -> Any? in
task.result?.users?.forEach({ (u: AWSCognitoIdentityProviderUserType) in
print("username=\(String(describing: u.username))")
})
return nil
})
Info.plist references all of the key properties for Cognito. Is it possible that listUsers and adminGetUser are reserved in some way?
Also, for the USERNAME_GOES_HERE stub, I always place a Cognito username, but not a Cognito ID or sub UUID. Does that work? (It seems odd that the username is required, since I thought in Cognito that username is not necessarily unique across Cognito users.
Thanks.
AdminGetUser and ListUsers are APIs which should be accessed from you backend and the calls must present valid IAM credentials.
You should not be calling these APIs from your iOS app. Instead use iOS Mobile SDK methods which will allows your users to sign in and then call the appropriate Cognito User Pools APIs.
As to why your calls fail, it seems you are trying to provide required IAM credentials using the Federated Identity service temporary credentials. You are instantiating the credential provider with identity pool but passing the user pool id instead of identity pool id. Refer to this answer for more explanation.

Authorization session always ask for user password in helper

I have an Objective-C application (https://github.com/NBICreator/NBICreator) with a privileged helper tool.
I have a few different privileged tasks the helper will need to perform during one build, and I want to have the user authenticate only once to perform those tasks.
The authorization works, but I can't seem to reuse the session in the helper. The user always have to authenticate for every step, even if I supply the exact same right to the Security Server and use the same AuthenticationRef.
I have read the docs and tested the pre-authentication methods in the main app first, and tried printing out (and retaining the auth session in the helper as well). But nothing I've tried have yet to work successfully.
I need som help figuring out why the Security Server feel the need to reauthenticate.
The code on GitHub in the Master Branch is current, and what I'm trying and testing by changing things back and forth. With that code, the user have to authenticate each time I call a helper function, even if I use the same authentication right.
This is what the right looks like in the authorization database:
class = rule;
created = "470329367.933364";
"default-prompt" = {
"" = "NBICreator is trying to start an Imagr workflow.";
};
identifier = "com.github.NBICreator";
modified = "470329367.933364";
requirement = "identifier \"com.github.NBICreator\" and anchor apple generic and certificate leaf[subject.CN] = \"Mac Developer: Erik Berglund (BXUF2UUW7E)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */";
rule = (
"authenticate-admin"
);
version = 0;
This is where I define the right: https://github.com/NBICreator/NBICreator/blob/master/NBICreator/Helper/NBCHelperAuthorization.m#L36-L43
NSStringFromSelector(#selector(authorizeWorkflowImagr:withReply:)) : #{
kCommandKeyAuthRightName : #"com.github.NBICreator.workflowImagr",
kCommandKeyAuthRightDefault : #kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc : NSLocalizedString(
#"NBICreator is trying to start an Imagr workflow.",
#"prompt shown when user is required to authorize to add a user"
)
},
And this is where I check if the user is authenticated:
https://github.com/NBICreator/NBICreator/blob/master/NBICreator/Helper/NBCHelperAuthorization.m#L222-L253
+ (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command authRef:(AuthorizationRef)authRef {
#pragma unused(authData)
NSError * error;
OSStatus err = 0;
AuthorizationItem oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = [#"com.github.NBICreator.workflowImagr" UTF8String];
err = AuthorizationCopyRights(
authRef,
&rights,
NULL,
kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
NULL
);
if ( err != errAuthorizationSuccess ) {
NSString *message = CFBridgingRelease(SecCopyErrorMessageString(err, NULL));
error = [NSError errorWithDomain:[[NSProcessInfo processInfo] processName] code:err userInfo:#{ NSLocalizedDescriptionKey : message }];
}
return error;
}
As you can see there, I'm testing by setting a hardcoded right name to try and resue that right to the Security Server.
I'm stumped right now, and can't seem to find a way forward. Hoping someone here might know where to look.
I found the answer in how I set up the right in the rights database.
I was using the default code from the Apple example EvenBetterAuthorizationExample for creating and using the rights. But that only points to this documentation for the different rights to use.
None of those were helping me with authenticatin once and the having the helper be authenticated the next time I request authentication for the same right.
After some digging and looking into the actual rules in the authorization database I found a way to copy the rule that AuthenticateWithPrivileges uses, that works by authenticating for 5 minutes until requiring re-authentication.
So, I changed my code for creating my custom right from this:
NSStringFromSelector(#selector(addUsersToVolumeAtPath:userShortName:userPassword:authorization:withReply:)) : #{
kCommandKeyAuthRightName : NBCAuthorizationRightAddUsers,
kCommandKeyAuthRightDefault : #kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc : NSLocalizedString(
#"NBICreator is trying to add a user.",
#"prompt shown when user is required to authorize to add a user"
)
},
To this:
NSStringFromSelector(#selector(addUsersToVolumeAtPath:userShortName:userPassword:authorization:withReply:)) : #{
kCommandKeyAuthRightName : NBCAuthorizationRightAddUsers,
kCommandKeyAuthRightDefault : #{
#"class": #"user",
#"group": #"admin",
#"timeout": #(300),
#"version": #(1),
},
kCommandKeyAuthRightDesc : NSLocalizedString(
#"NBICreator is trying to add a user.",
#"prompt shown when user is required to authorize to add a user"
)
},
So, instead of using #kAuthorizationRuleAuthenticateAsAdmin as the RightDefault, i passed in this dict:
#{
#"class": #"user",
#"group": #"admin",
#"timeout": #(300),
#"version": #(1),
},
After that, my helper can request authorization from the user, and then I can reuse that session for the same right during 5 minutes without asking the user again.

Claims based authorization with ASP.NET MVC

I was reading up a lot of blog posts and stackoverflow answers but still I am unable to find a real world open source project which uses claims based authentication and authorization, so that I can get an idea on how to actually implement these.
So far what I could find is Thinktecture.IdentityModel and this blog implements a claims based authorization on a sample website. If you guys could point me some Open source projects using claims, that would be really helpful.
What I am interested is how to retrieve claims for my application using the database.
So far, what I have tried is that using an in memory claims store to simulate the databsae, I have created a CustomClaimsTransformer and CustomAuthorisationManager like this.
public class CustomClaimsTransformer : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
//validate name claim
string nameClaimValue = incomingPrincipal.Identity.Name;
return CreatePrincipal(nameClaimValue);
}
private ClaimsPrincipal CreatePrincipal(string userName)
{
int userId = ClaimStore.Users.First(u => u.Value == userName).Key;
var claims = ClaimStore.ClaimsSet.Where(c => c.Key == userId);
var claimsCollection = claims.Select(kp => kp.Value).ToList();
return new ClaimsPrincipal(new ClaimsIdentity(claimsCollection, "Custom"));
}
}
public class CustomAuthorisationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
string resource = context.Resource.First().Value;
string action = context.Action.First().Value;
if (action == "Show" && resource == "Code")
{
bool likesJava = context.Principal.HasClaim(ClaimStore._httpMyclaimsUsers, "True");
return likesJava;
}
else if (action == "Read" && resource == "Departments")
{
bool readDeps = context.Principal.HasClaim(ClaimStore._httpMyclaimsDepartments, "Read");
return readDeps;
}
return false;
}
}
How to implement these in a real world scenario without having too many IF conditions?
Try the following link , it seems like a decent solution
http://developers.axiomatics.com/blog/index/entry/custom-claims-based-authorization-in-net-using-axiomatics-pep-sdk-for-net.html
Also you can define your policy and load it
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthorizationmanager.loadcustomconfiguration.aspx
How to: Implement Claims Authorization in a Claims-Aware ASP.NET Application Using WIF and ACS
http://msdn.microsoft.com/en-us/library/gg185907.aspx
I finally managed to design my own system with the required functionality using the existing asp.net identity 2.0 tables + a few of my own.
I'm gonna call every AREA-CONTROLLER-ACTION trio as resources in my system. WebAPI included. Area itself is a resource. Controller itself is a resource. Action itself is a resource. Any combination of them, is also a resource. I'll auto generate everything from the system itself using reflection.
Also, I'm going to use the same AspNetRoles table to store my User Groups. Users belong to one or more groups (Super Admin, Admin, Agent, Client etc.).
Using the existing Role based model as a user group based model with claims, I could get it working.Super admins are on god mode. They can create lower level users/groups/assign permissions etc.
Users can have special permissions. For example, Everyone in Agent group is denied access to updating a hotel, but a special agent who might also be the owner of a hotel can be given specific access to updating only their hotel.
Since the entire access control system runs on MVC area-controller-action sets. No one initially has no access (including super admins) and we gradually define which parts the groups/users has access to. And we give super admins and admins exclusive access through a claim. Access to everywhere is denied by default.
Once I Auto generated the AREA-CONTROLLER-ACTION sets, I let the user select which group has access to which item.
When the user logs in, I get all the resources the current user has access to and store them as claims. Based on that, using a claims auth manager, when a user request access to some resource, I can check their claims and decide if they should be given access to.
foreach(var claim in permissionClaims) {
var parts = claim.Value.Split(new [] {
'|'
}, StringSplitOptions.None);
if (parts.Length == 3) {
//var httpMethod = parts[0];
var action = parts[1];
var api = parts[2];
//Current.Log.Warn("Checking Access : " + req + " [action: " + action + "]");
// is this request for a API action?
if (api.Contains("API")) {
// if so, req must be for a API action
if (req.Contains("Api") && action.Contains(req)) {
Log.Trace("User has access to API : " + req + " [action: " + action + "]");
return true;
}
} else {
// this is for a MVC action
if (action.Contains(req)) {
Log.Trace("User has access to MVC : " + req + " [action: " + action + "]");
return true;
}
}
}
}
I have explained the approach in detail here - ASP.NET MVC Fine Grained Identity & Access Control.

viaRemember not work - laravel

Auth :: attempt works perfect, but when you pass the second parameter "true" apparently does not care or does not recover with viaRemember
viaRemember fails to work, check this
controller User
`$`userdata = array(
'email' => trim(Input::get('username')),
'password' => trim(Input::get('password'))
);
if(Auth::attempt(`$`userdata, true)){
return Redirect::to('/dashboard');
}
view 'dashboard', always show 777
#if (Auth::viaRemember())
{{666}}
#else
{{777}}
#endif
I have hit the same obstacle, so looking into the code one can see that viaRemember is not meant to be used as a function to check if the user was logged into the system in one of all the ways a user can be logged in.
'viaRemember' is meant to check if a user was logged into the system specifically via the `viaRemember' cookie.
From what I gather, authentication of user is remembered in two ways:
a via remember cookie.
The cookie value is compared to the via remember field in the users table.
a session cookie.
The cookie value is used in the server to get the session from the
session store. On the session object from the store there is data attached. One of the
data items is the user id connected to the session. The first time
the session was created, the system attached the user id to the data
of the season.
In Illuminate\Auth\Guard class:
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
}
return $this->user = $user;
}
The getUserByRecaller function is called only if the session cookie authentication did not work.
The viaRemember flag is only set in the getUserByRecaller function. The viaRemember method is only a simple getter method.
public function viaRemember()
{
return $this->viaRemember;
}
So in the end, we can use Auth::check() that does make all the checks including the viaRemember check. It calls the user() function in the Guard class.
It seems also the viaRemember is only an indicator. You need to do a type of Auth::check() the will get the process of authentication started and so the user() function will be called.
It seems that your project is on Laravel 4.0 but viaRemember() is added in Laravel 4.1! So that's expected.
in config\session.php file change the 'expire_on_close' = false to true and once you close restart your browser, it must be ok.