ADALiOS - how to refresh accessToken silently? - ios

I'm using ADALiOS v3.0.0-pre.2 to connect to Azure AD B2C and authorize a given user. I successfully obtain an accessToken for the user, who gets prompted with UI to login in the process. I use the acquireTokenWithScopes method on ADAuthenticationContext instance to do so.
Somewhere down the line, I want to make sure that the accessToken I obtained earlier is still valid and hasn't been revoked. So, I use acquireTokenSilentWithScopes to check. However, I get an immediate error back saying:
Error raised: 10. Additional Information: Domain: ADAuthenticationErrorDomain Details: The user credentials are need to obtain access token. Please call the non-silent acquireTokenWithResource methods.
What's the right usage of this API such that the token gets silently refreshed or throws an error only when it has been revoked on the server side?

I've managed to beat acquireTokenSilentWithScopes into submission by making the following changes to ADALiOS v3.0.0-pre.2.
Change #1:
ADUserIdentifier has the following class method:
+(BOOL) identifier:(ADUserIdentifier*)identifier matchesInfo:(ADProfileInfo*)info
In it, there are the following lines of code:
NSString* matchString = [identifier userIdMatchString:info];
if (!matchString || [matchString isEqualToString:identifier.userId])
{
return YES;
}
For one reason or another, matchString can sometimes come back as NSNull and calling isEqualToString: method on it will throw. I changed it thusly:
id matchString = [identifier userIdMatchString:info];
if (!matchString || ![matchString isKindOfClass:[NSString class]] || [matchString isEqualToString:identifier.userId])
{
return YES;
}
This seems like a legit bug in the framework that's worth fixing.
Change #2:
When a token is received from AD, ADALiOS tries to store that value in the cache. At some point, it calls ADTokenCacheStoreItem's userCacheKey property, which is defined as follows:
-(NSString*)userCacheKey
{
switch (_identifierType)
{
case OptionalDisplayableId:
case RequiredDisplayableId:
return _profileInfo.username;
case UniqueId:
return _profileInfo.subject;
}
}
In my case, I use RequiredDisplayableId to identify users. In the switch statement above, that translates to _profileInfo.username, which, in turn, returns the preferred_username value from the user profile dictionary. For me that value is not set. So, userCacheKey returns NSNull and the caching mechanism fails.
The values that are set in the user profile dictionary are name and tid. This could be a server misconfiguration, but I worked around the issue by changing the return value of this method to _profileInfo.friendlyName (which maps to name in the user profile dictionary).
Change #3:
The ADKeychainTokenCacheStore, which I use as the concrete ADTokenCacheStoring cache of choice, exposes a sharedGroup property that allows multiple applications to share common keychain secrets. By default, sharedGroup is set to com.microsoft.adalcache. However, since the class is currently private, there is no way to override this value. Also, having that value set requires the iOS app to declare the shared group name in its entitlements. Without these entitlements properly configured, setting values into the keychain fails. So, to work around this issue, I manually set the default sharedGroup value to nil in the ADKeychainTokenCacheStore class itself. I suspect eventually this class will be exposed by the framework as public, but currently that's not the case so I had to hack into it.
Change #4
When I request an auth token from the AD server via the ADALiOS framework, I do so using a policy and a set of scopes. The framework code uses this policy/scope pair to create a lookup key and see if any tokens for that key have already been cached. If none are found, the code contacts the server as expected. Once the server returns an auth token, the framework attempts to cache the value. It constructs a brand new policy/scope key object. However, this time, it uses the policy and scope values that are returned by the server, not the ones I passed in. And, for some reason, the server returns those values to nil. As a result, the new policy/scope key that gets constructed for storage is valid but different from the one I used to look up the cached token initially. So, while the caching operation succeeds, next time I try to look up the auth token using my valid policy/scope pair, the lookup fails.
This may, once again, be a server misconfiguration issue.
Regardless, to fix the problem, I now reset the policy and scope values in the response from the server to the original values I used to generate the server request in the first place. This happens in the following method in ADAuthenticationContext(TokenCaching):
- (void)updateCacheToResult:(ADAuthenticationResult*)result
cacheInstance:(id<ADTokenCacheStoring>)tokenCacheStoreInstance
cacheItem:(ADTokenCacheStoreItem*)cacheItem
withRefreshToken:(NSString*)refreshToken
After all these changes, getting the AD auth token and refreshing it silently seems to work as expected. I'm a little worried about how much I needed to hack into the codebase to make it work. It would be helpful if some MS folks could direct me as to whether these changes were warranted or whether there is a more straight-forward solution.
UPDATE:
It turns out that you don't need to hack into ADKeychainTokenCacheStore directly (change #3 above). The ADAutheticationSettings class exposes a method for you to do so thusly:
[[ADAuthenticationSettings sharedInstance] setSharedCacheKeychainGroup:nil];

I'm Brandon Werner from the Azure Active Directory team. I answered this question here: https://stackoverflow.com/a/44170226/1232116 for the specific question asked.

Related

how do you request a new downloadUrl from graph-onedrive?

In my C# code with the Graph SDK, I am testing "download large file" with a method generously provided here: Download large files from OneDrive using Microsoft Graph SDK
However, even though I explicitly request the DriveItem from the service just prior to calling this method (which then uses the downloadUrl from the AdditionalData in the DriveItem), I am getting a response indicating JWT Token Expired.
I assume this token is embedded in the pre-authenticated downloadUrl. Further, I theorize that this token is either single-use or it expires relatively quickly...both reasons for me to explicitly request the DriveItem just before attempting to utilize that URL.
How do I ensure that the service is sending me back a "fresh" downloadUrl? Are there some no-cache headers or something I should explicitly include in my DriveItem request?
As mentioned, even though I have the DriveItem object local, I am explicitly re-fetching it from the service to ensure I have the latest properties, and a new downloadUrl. (But this is not working.) Using the Request object without any extra Headers (or other odata parameters) is apparently not enough to trigger the generation of a new URL.
This does not happen every test. I believe it happens when re-running a test with the same DriveItem in a short time-window. Again, I'm not sure if the JWT token is single-use or time-expired, but if it's expired in any case, I think the service should automatically generate a new one. If that's not practical, just understanding how to explicitly ask for a new one is certainly effective too!
Continuing to debug and try different things, I believe I have found the answer to my question...
Sending a known-false ETag in the request for the DriveItem seems to force the service to send me a new copy. Example code as follows:
//we need a "recent" copy of this object, because the downloadURL is only good for a limited time...
IList<HeaderOption> opts = new List<HeaderOption>();
opts.Add(new HeaderOption("Cache-Control", "no-cache, no-store"));
opts.Add(new HeaderOption("if-none-match", "xyz")); //never match
DriveItem item = await client.Me.Drive.Items[Id].Request(opts).GetAsync();
if (item == null)
{
log.Warn("Could not fetch existing driveItem? " + Id);
return null;
}
object downloadUrl = null;
item.AdditionalData?.TryGetValue(#"#microsoft.graph.downloadUrl", out downloadUrl);
I am still testing this, but initial tests show it to work. I will update this answer when my testing is reasonably conclusive.
Still "bonus points" for anyone who can identify the specific expiration of these URLs (time, number of uses, etc.). Then we can actually check locally whether our object is "stale" and only re-fetch when necessary.

How to make app (with username/password login) know that a user is logged in?

I'm trying to make a login screen and made a database with Firebase by Google. The way I tried to make accounts in database is just by making a new child which is "Users" and then the next child would be a user (which is uniquely defined with a unique username). Then, user has other attributes and an attribute called loggedIn which is set to String 'false' but when on loginScreen login goes successfully set to String 'true'. How can I know after login and when that LoginViewController goes away which account has logged in exactly on that phone (simulator at the time) because there can be more users at one time with the attribute value 'loggedIn' set to 'true' and because of that can't go back to database to check that. I'm really new at this and don't know if this whole approach is okay by making in real-time database something like that and actually checking that by the attribute. Maybe I have to use some kind of a local database or something similar?
Swift 4, iOS development, Xcode 9
If you just want to persist locally the login status you may use UserDefaults. Just have a key called currentUser and set the value as his unique Id.
Once a user logs out just clear that key.
You can also use that to determine whether to launch the login screen when the app opens.
But it may be better to use something like AWS Cognito or similar services to handle user management.
Are you building an iOS app only or a cross-platform project?
I'm not sure this is the perfect answer for you. However, some of my apps use CloudKit and the login is authenticated and only then opens up areas of the app on the user's device. I'm sure if you wanted to check the status of successful logins, the authentication on the cloud could update as confirmation and you could subsequently access the cloud database using the CloudKit Dashboard.
It's quite intuitive and works seamlessly with Xcode.
I have a class called LoginManager, defined as an NSObject. Included in there is an optional String variable called accessToken with setter and getter functions for saving and retrieving my access token, including an option for clearing the token which would be used upon logout. There is also a function to check the status of the key.
var accessToken: String? {
get {
return //key from storage
}
set {
if newValue == nil {
//remove key from storage
} else {
//save key to storage
}
}
}
func isUserLogedIn() -> Bool {
return self.accessToken != nil
}
In my own case, I have an application manager object that changes the root view controller based on this info.
LoginManager.singleton.isUserLogedIn() ? loadMainView() : loadLoginView()
So the resulting view controller is never loaded unless the user is logged in. Is that useful to you?

kSecUseAuthenticationUISkip how to use it

I want to add an item to secItemAdd with following access control
SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleAfterFirstUnlock,
/*kecAccessControlTouchIDAny | */kSecAccessControlApplicationPassword, &error);
So it basically asking password for adding item to keychain, that is perfect but when I want to access this Item I want to skip UI authentication and somwhow I wnat to provide the password manually with use of kSecUseAuthenticationUISkip.
As per Apple's documentation on the kSecUseAuthenticationUISkip constant (https://developer.apple.com/reference/security/ksecuseauthenticationuiskip?language=objc)
Silently skip any items that require user authentication. Only use this value with the SecItemCopyMatching function.
So it's not for selectively skipping the authentication, but rather for skipping items that require authentication
Now for your case when you want to secure access to the keychain with an app provided password, when calling SecItemAdd you'd have to use the kSecUseAuthenticationContext key set to a LAContext instance of type LACredentialTypeApplicationPassword that holds the password you ask from your user in conjunction with the kSecAccessControlApplicationPassword in the object you pass to the kSecAttrAccessControl key.
Here'a an article that it explains it with an example that I think it fits your use case pretty well: http://collinbstuart.github.io/lessons/2015/11/04/keychain_entropy

Is HKMetadataKeyWasUserEntered broken? I keep getting nil when there is data in the health app

I'm trying to grab data from the Health App. Specifically data that the user did not enter in themselves. For instance, I have an iPhone 6+ that logs the amount of steps that I take. There is also an option to add the data manually; If you add the data manually, the health app marks the data as "user added".
Here's what's confusing me. Let's say I added a step count of 22. When I query the data using HKStatisticsQuery with a predicate of
HKQuery.predicateForObjectsWithMetadataKey(HKMetadataKeyWasUserEntered, allowedValues: [true])
I get the correct result of 22 steps, since I set the allowedValues to true and that I added this myself. However, when I try to set allowedValues to false, I get no results
HKQuery.predicateForObjectsWithMetadataKey(HKMetadataKeyWasUserEntered, allowedValues: [false])
I do indeed have the step data in the health app, but it returned no results.
Check for the below possible areas to fix it:
did you authorized your application to access Steps data from HealthKit?
If your quering for steps count from your application without authorizing with HealthKit, then HealthKit will not return any exception it will simply returns the steps count which was entered from your application only if available, otherwise returns nil.
Before going to query health data, check for the authorization status for steps count using authorizationStatusForType: method available with HKHealthStore class.
Update 1:
My observations on wasUserEntered key is:
If user entering steps data from HEALTH app, respective HKQuantitySample stores metadata dictionary along with HKWasUserEntered key as TRUE automatically.
If user entering steps data from other than Apple's HEALTH app, respective health/fitness device or our application should send metadata dictionary with key HKWasUserEntered along with value as either TRUE/FALSE. Otherwise, the metadata property will contain nil object. Hence, Apple is not applying predicate(predicate contains metadata key) on the data which don't have metadata with it.
For debugging this metadata, try to print your HKQuantitySampleObject.metadata
Apple's implementation on metadata Vs NSPredicate:
If Health data observed from Health/fitness devices, HealthKit is not adding the metadata dictionary to the respective health record.
In case of Health applications other than Apple's Health app, the developer should manually add the metadata dictionary for his record of health data.
If there is no metadata for a specific health record and NSPredicate have a constraint on metadata then, HealthKit completely omitting to validate such records.
Finally,
It is advised to use
(instancetype)quantitySampleWithType:(HKQuantityType *)quantityType
quantity:(HKQuantity *)quantity
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate
metadata:(NSDictionary *)metadata;
instead of
+ (instancetype)quantitySampleWithType:(HKQuantityType *)quantityType
quantity:(HKQuantity *)quantity
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate;
to add metadata.
Reporting Apple regarding this bug that, predicate(which contains metadata key) should be applied on all the data irrespective of checking for metadata exists or not.
It's been a while and you have probably solved this by now, but I think it is worth posting here in case someone else comes across the same issue.
Like Bhanu mentioned, data created by Apple themselves doesn't seem to have the HKWasUserEntered entry as part of its metadata.
But Apple never claims to do it anyway in their docs, so the proper query would be one that filtered out items with HKWasUserEntered == true if that key was set, but also gave back everything else that had no metadata associated.
The ideia is that metadata is a Dictionary, and that type returns nil if a key does not exist. So there are three possible scenarios:
HKMetadataKeyWasUserEntered == nil
HKMetadataKeyWasUserEntered == false
HKMetadataKeyWasUserEntered == true
Out of those, you just want to filter out the last one, since that explicitly tells you that data was entered but the user.
This can be done using the + predicateForObjectsWithMetadataKey:operatorType:value: convenience method on HKQuery, like this:
HKQuery.predicateForObjects(withMetadataKey: HKMetadataKeyWasUserEntered, operatorType: .notEqualTo, value: true)
But of course, your initial question was: "Is HKMetadataKeyWasUserEntered broken?". And I don't believe it is, since, like mentioned, Apple never claims to write that key for their own values. :)

need session variables in valueUnbound?

I am using valueUnbound method of HttpSessionBindingListener to release lock(an entry from the database), before session is about to expire:
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
String user = (String) event.getSession().getAttribute("currentUsr");
removeLock(user);
}
When the lock is set, I am setting up the username as a session variable.
I need this "username" in my remove lock method. But the getAttribute is throwing an exception:
java.lang.IllegalStateException: getAttribute: Session already invalidated
I need help in getting the session variable?? or is there any other way to get the username?
No, since session has been invalidated.
Although, I figured out the solution, I am setting the attribute via servlet context in
valueBound method and getting it through the : event.getSession().getServletContext().getAttribute("cUser");
it works fine. Thank You EJP
I got your point EJP, you are right , I am making it complex, I can get it from event.getValue() . +1 to your answer, Thank You.
Although, I figured out the solution, I am setting the attribute via servlet context in valueBound method and getting it through the : event.getSession().getServletContext().getAttribute("cUser");
So.. You are storing session scoped data in the application scope. Do you realize that this way the data is shared among all visitors of the webapp? Visitor X would then see the attribute set by visitor Y which has visited the website at a later moment. It makes the problem only worse.
Anyway, as to the concrete problem, as the exception message is trying to tell you, the session has already been invalidated at that point. There are two ways to solve this:
Make currentUsr a property of the class which is implementing HttpSessionBindingListener, so that you don't need to grab it as a distinct session attribute.
Use a HttpSessionListener instead. The sessionDestroyed() method is called right before invalidation, so you should still have access to all attributes.

Resources