Request, Response, Challenge filters in HttpConversationManager
The HttpConversationManager filters
When we were starting the design for our next-generation network component, the HttpConversationManager, we looked extensively at simply reusing and extending the two common iOS networking components: namely, NSURLSession, and AFNetworking. We’re very conscious of the work in the AFNetworking community, building on NSURLConnection and NSOperation, opening side-modules for OAuth, web-sockets, implementing pinning, etc. Also, the modern NSURLSession APIs that came with iOS7, with the “data task” concept, file transfer, background activity, block interfaces, integrated Kerberos, gave us confidence that most enterprise-grade connectivity cases could be implemented with high-level native API’s. This was really our goal: if iOS, Android, and Windows OS’s provided enterprise-grade API’s, reuse them!
The limitation we hit on both of these options was native support for SAML2, which is critical in many enterprise landscapes, and for SAP’s HANA Cloud Platform identity provider. Also, we commonly have unique requirements around custom SSO tokens, etc., which need to be ordered in the HTTP request procedure. So, taking some ideas from the open-source community, we implemented a very thin wrapper on NSURLSession with NSURLSessionDataTask, which is now the HttpConversationManager.
I’ve written some here about some of the concepts in HttpConversationManager. What I’d like to share in this post is an explanation of the three filter types that can be added to the HttpConversationManager, to do pre- and post- request processing, and handle authentication challenges.
For most cases, no pre/post-processing is required, so you can support Basic and Client Certificate authentication by adding a single filter (Challenge). SAML2 authentication is a post-processing operation, and requires a single Response filter. OAuth2 requires both a Request and Response filter. SAP has already implemented these: as a developer, you just need to set them to your HttpConversationManager. For custom SSO procedures, you can order your custom filter(s) around the regular authentication types as necessary.
Filters on a HttpConversationManager
In short, there are three filter types: the RequestFilter, ResponseFilter, and ChallengeFilter.
- The RequestFilter is processed before the request is sent over the network, and can modify the request. Also, the original request can be paused, while a 2nd operation is completed (like obtaining a token from a 3rd-party service, then re-started when the RequestFilter is complete.
- The ChallengeFilter is processed next in the HTTP request, if the server responds with a HTTP authentication challenge. When a ChallengeFilter is executed, it should return a credential in response to the challenge. This is identical to supplying a credential in the NSURLSession
-didReceiveChallenge:(NSURLAuthenticationChallenge)challenge
delegate method. - The ResponseFilter is processed last in the HTTP request flow, after the response is complete. Like the RequestFilter, it allows you to inspect the response payload, and complete additional operations, before passing the finished response to the app. Unlike the RequestFilter, you cannot modify the payload of the response.
Request flow, from Application to Service Provider
The RequestFilter
/**
Delegate method called before executing the request.
@param mutableRequest: the request instance which will be executed
@param conversationManager: copy of HttpConversationManager instance, can be used for starting additional requests
@param completionBlock: call the block when the filter finished the modification of <i>mutableRequest</i> object
*/
-(void) prepareRequest:(NSMutableURLRequest*)mutableRequest conversationManager:(HttpConversationManager*)conversationManager completionBlock:(void (^)())completionBlock;
The interface for creating a new request on HttpConversationManager
-(void) executeRequest:(NSMutableURLRequest*)urlRequest completionHandler:(void (^)(NSData* data, NSURLResponse* response, NSError* error))completionHandler
takes a NSMutableURLRequest as an input parameter. The RequestFilter may touch that mutableRequest instance, to add headers, etc.
The RequestFilter -prepareRequest:
method also passes a copy of the HttpConversationmanager, which “can be used for starting additional requests”. This is important, if, for instance, you were using OAuth2, and knew that each request must include a Bearer auth token. If the auth token isn’t available, you need to call an endpoint on the IdP in order to get it–before sending the original request to its destination. Otherwise, the original request will fail with a 403. So, you could use the conversationManager
instance to execute this secondary request.
Using a copy of the original HttpConversationManager for these types of secondary requests is convenient, but not required. The convenience factor comes from the fact that your remaining Request, Challenge, and Response filters are still attached to the conversationManager
instance, so you don’t need to create a new conversation, configure it, etc. However, it is important to know that the conversationManager
instance variable is a copy of the original HttpConversationManager, and that the completed and in-progress RequestFilters have been removed from the instance’s requestFilter
array. So, you don’t need to worry about the infinite loop problem, but you should think for a moment to check that your secondary request on this conversationManager
instance doesn’t expect any discarded RequestFilters.
In general, this should never be a problem. If a complex case existed which required all the original filters for a secondary request, you could simply initialize a new HttpConversationManager, configured specifically for this request.
Once the NSURLMutableRequest is completely prepared by the RequestFilter, then you should invoke the void completionBlock()
. The next RequestFilter in the HttpConversationManager will then be executed, according to the order of the HttpConversationManger allRequestFilters
array.
The ChallengeFilter
/**
Delegate method called when authentication challenge occurs.
@param challenge: NSURLAuthenticationChallenge
@param conversationManager: copy of HttpConversationManager instance, can be used for starting additional requests
@param completionBlock: call the block when the filter finished its job. <br> Return YES, and a NSURLCredential object, if the challenge is handled, or return NO and nil, if the challenge is not handled. If return YES and NSURLCredential object is nil, it will be handled as no credential provided.
*/
-(void) handleChallenge:(NSURLAuthenticationChallenge*)challenge conversationManager:(HttpConversationManager*)conversationManager completionBlock:(void (^)(BOOL useCredential, NSURLCredential* credential))completionBlock;
The ChallengeFilter is *only* executed when the NSURL loading system invokes the handler of NSURLAuthenticationChallenge. Inside the HttpConversationManager, after the RequestFilters are all completed, a NSURLSessionDataTask is created from the NSMutableURLRequest, and sent to the server. When the NSURLSessionDelegate method
– (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
is invoked, the array of `challengeFilters` is enumerated.
The SAP SDK ships with standard ChallengeFilters for UsernamePassword (Basic) and ClientCert. When they are invoked, they check the protectionSpace.authenticationMethod
value of the NSURLAuthenticationChallenge, to see if they should try to supply a credential. For example, if the authenticationMethod == NSURLAuthenticationMethodClientCertificate
, the ClientCertChallengeFilter will try to supply a credential.
Adding CredentialProviders
The way that the ChallengeFilters find the credentials to supply is through an array of attached Providers. A Provider is a protocol which a developer can implement, with their own customizations to match their particular landscape, MDM/MAP provider, SCEP provider, etc. These can be as simple as just returning a hard-coded username/password, showing a secure-style UIAlertView, or more complex: calling a 3rd-party API to get a client certificate, showing a custom UI, etc.
The simplest version is a UsernamePasswordProviderProtocol, which can be as easy as just returning a NSURLCredential with username & password from code.
– (void)provideUsernamePasswordForAuthChallenge:(NSURLAuthenticationChallenge *)authChallenge completionBlock:(void (^)(NSURLCredential *, NSError *))completionBlock
{
NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:@”myName”
password:@”myPassword”
persistence:NSURLCredentialPersistenceForSession];
completionBlock(credential, nil);
}
The ClientCertProviderProtocol has a similar signature: after obtaining the client certificate from the keychain, DataVault, 3rd-party API, etc., pass it into the completion block as a NSURLCredential:
– (void) provideClientCertForAuthChallenge:(NSURLAuthenticationChallenge*)authChallenge completionBlock:(void (^)(NSURLCredential*, NSError*))completionBlock
{
// obtain the certificate(s) and construct SecIdentityRef
NSURLCredential *cred = [[NSURLCredential alloc] initWithIdentity:mySecIdentityRef
certificates:@[certificates]
persistence:NSURLCredentialPersistenceForSession];
completionBlock(credential, nil);
}
For the standard Basic auth & client certificate auth cases, I’d recommend just using the ChallengeFilters out-of-the box from the SDK, implementing the Provider to supply the user credentials.
If you’re implementing your own ChallengeFilter (this is ok, even for the standard types, so long as you substitute your filters for the standard ones on the HttpConversationManager, or order them in-front of the standard ones) then you might not bother with using the Provider protocol–you might just supply the credential directly from within the filter. In this case, make sure that the NSURLCredential is passed into the -handleChallenge:
completionBlock.
…
// filter code, and obtain the NSURLCredential
completionBlock(BOOL useCredential, NSURLCredential* credential);
}
The ResponseFilter
The response filter behaves exactly like the request filter, except it is executed on a successful NSURL response, and the contents of the response are not mutable.
The developer has access to the NSURLResponse and NSData payload, and can execute custom read procedures before calling the completion block. The primary use case for the response filter is trapping response payloads related to authentication operations. To this end, the method signature contains a parameter named shouldRestartRequest
, which should be set to YES in the event that the response contains an authentication token, or an authentication redirect (as in the SAML2 case), and the original requests can now be successfully authenticated.
In general, the response filter should not be used for handling the response payload, as the developer expects to get the successful payload in the completion block of his request. But, the response filters are executed before payloads are passed to the parser under the SODataStore, so they can be an effective way to trap error responses, especially HTML content, from a server–before it ends up in the OData parser.
Summary
In summary, the HTTPConversationManager’s Request- and ResponseFilters give you the ability to touch requests from the application before they go to the server, and handle the responses from the server before they’re returned to the application. The ChallengeFilters give you the ability to respond to an NSURLAuthenticationChallenge, in the same way you normally would within the NSURLSessionDelegate. To implement your own filters, conform to the protocol, and add them to the array on the conversation Manager.
Dear Stan,
thanks for the very good block post. It give us a very good understanding of the
HttpConversationManager it self and his usage.
But one question do we have. We open a onlineStore and create a HttpConversationManager and implement the ChallengeFilterProtocol where we set the credentials. If we install the app new the ChallengeFilterProtocol is called from the HttpConversationManager. But if we close the app and restart it the ChallengeFilterProtocol isn't called again but the onlineStore is open successful again. Do the HttpConversationManager save the credentials? We use the NSURLCredentialPersistenceNone constants.
Could you help us for understanding the workflow completely?
Thanks André
Hi André Nehm, could you share:
1) if you are using the Logon component also?
2) is this on iPhone device, or in simulator?
3) what iOS version are you using?
4) the code snippet by which you are creating the NSURLCredential?
It sounds like a session cookie is being cached somewhere in the NSURL loading stack, that is not respecting the NSURLCredentialPersistence. Will investigate.
--Stan
Hi Stan, here are the Infromations:
1) no, we don't want to use the logon component
2) actual we try it only in the simulator, i can try it on the iPhone too
3) we use iOS8.1 and the deployment target is 7.1
4) here are the code snipped for the NSURLCredentials:
-(void)handleChallenge:(NSURLAuthenticationChallenge *)challenge conversationManager:(HttpConversationManager *)conversationManager completionBlock:(void (^)(BOOL, NSURLCredential *))completionBlock {
// filter code, and obtain the NSURLCredential
NSURLProtectionSpace * protectionScpace = challenge.protectionSpace;
NSString *realm = protectionScpace.realm;
NSURLCredential *credential = challenge.proposedCredential;
if ([realm isEqualToString:@"generic_USER_PASS_auth"]) {
credential = [[NSURLCredential alloc] initWithUser:@"SMPuser"
password:@"SMPpassword"
persistence:NSURLCredentialPersistencePermanent];
}
else {
credential = [[NSURLCredential alloc] initWithUser:@"BACKENDuser"
password:@"BACKENDpassword"
persistence:NSURLCredentialPersistenceNone];
}
completionBlock(TRUE, credential);
}
Hi Andre,
Thanks, clear summary.
I will say, that I do not see this code using the NSCredentialPersistenceNone enum. The credential that is created if realm == "generic_USER_PASS_auth" is currently created with persistence:NSURLCredentialPersistencePermanent.
The alternate credential created under the else() seems to be using a variable (BACKENDpassword), instead of the persistence enum directly. So, I'm not sure which value is passed.
Can you try the following:
1. put a breakpoint inside if([realm isEqualToString:@"generic_USER_PASS_auth"]), so that you confirm that the credential being created is SMPuser/SMPpassword
2. switch the NSURLCredentialPersistence for that credential to NSURLCredentialPersistenceNone
Then, check the behavior?
Dear Stan,
sorry for this. I replaced the wrong line with the "Backendpassword" placeholder. I fixed it so you can see that i use the NSURLCredentialPersistenceNone.
Here we have 2 login challenges, the first is for the SMP user with the generic_USER_PASS_auth what is set in the SMP and the second is for the SAP Backend.
The Login to the SMP should be stored permanently but the login to the backend should be set every time the user will synchronise and so we set it to persistence none.
Is this possible?
2. I check the brakepoint and the credentials are created correckly but only the first time when i install the app new, after this the store opened without to go in the ChallengeFilterProtocol.
Also if we set the NSURLCredentialPersistenceNone to both the store opened without to go in the ChallengeFilterProtocol.
Thanks André
Hi Andre,
A dual-authentication doesn’t occur: only one Basic challenge is emitted from the proxy. So, this concept of having a technical SMPUser is unnecessary.
With on-premise SMP, when you configure a backend connection URL for an application, to configure Basic authentication, you should:
1. Add a "Basic" SSO mechanism for the backend. No configurations are required.
2. Add an Authentication Provider of type "HTTP/HTTPS". On the Authentication Provider configuration, specify the URL which should be used for authentication. This may be identical to the backend connection URL, or may point to an SSO authentication URL.
If you are pointing to an SSO authentication URL, you may optionally identify attributes of the SSO cookie which should be submitted to the backend.
When the end user tries to authenticate against a service exposed by SMP, the authentication challenge comes from SMP, but is seeking the credentials required for the URL specified in the HTTP/HTTPS Authentication Provider. If the provided credentials fail, a 401 or 403 (depending on SMP version) from the backend will be propagated back to the client.
So, I would eliminate the SMPUser concept entirely. The backend connection should be configured with a Basic SSO provider and HTTP/HTTPS Authentication Provider, and this is the credential which should be supplied to the initial challenge.
Then, I would modify the NSURLCredentialPersistence values for that credential. Do you really want None, or perhaps Session?
Hi Andre, starting a new comment thread so we get the full width of the screen 🙂
Hi Stan, you are right thats much better 🙂 .
I musst update the SMP3Server and the SDK to the newest service pack levels (Server SP05 PL1 and SDK 06). But i did it and now i can configure the SSO Mechanism in the Backend Configuration.
The initial registration process is now against the Backend user credentials. That's very good.
But when i now open the onlineStore the ResponseFilterProtocol and SAML2ConfigProviderProtocol is called from the HttpConversionManager. The other protocolls like SODataOnlineStoreDelegate, SAML2ConfigProviderProtocol, ChallengeFilterProtocol, UsernamePasswordProviderProtocol isn't called.
So I cant set the credential in the ChallengeFilterProtocol function. The onlineStore is opened succesfull every time since i registered (initial) my user one time.
Where can i now define the NSURLCredentialPersistence that the user must authentificate every time if we open the store. Session would be anouth.
Initiallogin:
if (!state.isRegistered) {
NSError *error = nil;
MAFLogonContext *mafContext = [lc getContext:&error];
MAFLogonRegistrationContext *regContext = mafContext.registrationContext;
regContext.applicationId = [ConnectivitySettings SMPAppID];
regContext.serverHost = [ConnectivitySettings SMPHost];
regContext.serverPort = [ConnectivitySettings SMPPort];
regContext.backendUserName = self.usernameInput.text;
regContext.backendPassword = self.passwordInput.text;
regContext.isHttps = [ConnectivitySettings useSSL];
regContext.securityConfig = [ConnectivitySettings SMPSecurityConfiguration];
lc.logonCoreDelegate = self;
[lc registerWithContext:mafContext];
} else {
NSLog(@"Already registered");
//Call MainMenu Scene
[self performSegueWithIdentifier: @"ShowMainMenu" sender: self];
}
- (void)registerFinished:(NSError *)anError
{
if (anError == nil) {
MAFLogonCore *lc = self.appDelegate.lc;
NSError *error = nil;
MAFLogonContext *context = [lc getContext:&error];
MAFLogonRegistrationContext *regContext = context.registrationContext;
[lc persistRegistration:@"abcd1234" logonContext:context error:&error];
NSDictionary *settings = [regContext.connectionData objectForKey:@"keyMAFLogonConnectionDataApplicationSettings"];
NSString *appCID = settings[[NSString stringWithFormat:@"d:%@", kApplicationConnectionId]];
NSString *appCIDMessage = [NSString stringWithFormat:@"Application Connection ID: %@", appCID];
[Utilities showAlertWithTitle:@"Registration Succeeded" andMessage:appCIDMessage];
//Call MainMenu Scene
[self performSegueWithIdentifier: @"ShowMainMenu" sender: self];
} else {
NSLog(@"Error on registration");
NSLog(@"%@", anError);
NSString *errorText = [anError.userInfo valueForKey:@"NSLocalizedDescription"];
[Utilities showAlertWithTitle:@"Registrationerror!" andMessage:errorText];
}
}
open the onlineStore:
- (IBAction)openStore:(id)sender {
NSError *error = nil;
MAFLogonUIViewManager *logonUIViewManager = [[MAFLogonUIViewManager alloc] init];
NSObject<MAFLogonNGPublicAPI> *logonManager= logonUIViewManager.logonManager;
HttpConversationManager* httpConvManager = [[HttpConversationManager alloc] init];
[[logonManager logonConfigurator] configureManager:httpConvManager];
[httpConvManager addChallengeFilter:self];
//[httpConvManager addResponseFilter:self];
CommonAuthenticationConfigurator* commonConfig = [[CommonAuthenticationConfigurator alloc] init];
[commonConfig addSAML2ConfigProvider:self];
[commonConfig addUsernamePasswordProvider:self];
[commonConfig configureManager:httpConvManager];
MAFLogonCore *lc = self.appDelegate.lc;
MAFLogonContext *context;
if ([lc unlockSecureStore:@"abcd1234" error:&error]) {
context = [lc getContext:&error];
}
MAFLogonRegistrationContext *regContext = context.registrationContext;
SODataOnlineStore *onlineStore = [[SODataOnlineStore alloc]
initWithURL:[NSURL URLWithString:regContext.applicationEndpointURL]
httpConversationManager:httpConvManager];
[onlineStore setOnlineStoreDelegate:self];
[onlineStore openStoreWithError:&error];
}
#pragma mark - CommonAuthenticationConfigurator Delegates
-(void) provideSAML2ConfigurationForURL:(NSURL*)url completionBlock:(void (^)(NSString* responseHeader, NSString* finishEndPoint, NSString* finishParameters))completionBlock {
completionBlock(@"com.sap.cloud.security.login", @"/SAMLAuthLauncher", @"finishEndpointParam");
}
Thanks André
HI Andre,
There's a lot of duplication in this code, so let me try to simplify it for you. Also, something important to look for is the warning indicators next to any code like this:
[httpConvManager addChallengeFilter:self];
with messages like:
"Sending 'AppDelegate' *const __strong to parameter of incompatible type 'id<ChallengeFilterProtocol>'."
This is usually a sign that you have forgotten to declare the protocol on the class represented by 'self'. It has a big effect, because it generally means that the protocol methods will not be called, even if the delegate is correctly set. You can rectify this issue by adding the protocol to the interface declaration:
@interface AppDelegate () <UISplitViewControllerDelegate, UsernamePasswordProviderProtocol>
----
Choose between LogonConfigurator, or CustomAuthenticationConfigurator
The main issue that I see with your code is that you are configuring the httpConvManager twice. First, using
id<ManagerConfiguratorProtocol>[logonManager logonConfigurator]
, then again using a CommonAuthenticationConfigurator that also conforms to <ManagerConfiguratorProtocol>. You should only be doing one or the other.The reason is this: When you are creating your own CommonAuthenticationConfigurator, and adding your own filters, you are taking control of the filters and their providers. When you generate a configurator from Logon (the MAFLogonNGPublicAPI), it generates its own pre-determined filters and providers, which are designed to read credentials and settings from the regular Logon data vault and plists.
The Logon-generated configurator exists so that the Logon functionality which was already in the SDK will work nicely with the HttpConversationManager. We implemented the bindings using regular filters and providers for consistency. But, if you use the configurator from Logon (MAFLogonNGPublicAPI), you do not implement your own filters and providers.
In this case, if you do not want to use the Logon component, you should remove the code:
[[logonManager logonConfigurator] configureManager:httpConvManager];
[httpConvManager addChallengeFilter:self];
//[httpConvManager addResponseFilter:self];
Instead, the CommonAuthenticationConfigurator is the correct approach. But here, you are trying to do too much, by adding both a SAML2ConfigProvider and a UsernamePasswordProvider. It sounds like you are just using Basic auth, so you can omit the SAML2ConfiProvider entirely, and remove this line:
[commonConfig addSAML2ConfigProvider:self];
. Stick just with setting your UsernamePasswordProvider. This will be called when the connection receives a basic authentication challenge.So, I think that you can significantly simplify the setup of your HttpConversationManager, down to just the following:
1. Specify on whatever class is implementing your UsernamePasswordProvider, that it implements the protocol
@interface AppDelegate () <UISplitViewControllerDelegate, UsernamePasswordProviderProtocol>
2. Implement the UsernamePasswordProvider
(see section in post above)
3. Initialize a CommonAuthenticationConfigurator, and add your UsernamePasswordProvider
4. Use your CommonAuthenticationConfigurator instance to configure your HttpConversationManager.
HttpConversationManager* httpConvManager = [[HttpConversationManager alloc] init];
CommonAuthenticationConfigurator* commonConfig = [[CommonAuthenticationConfigurator alloc] init];
[commonConfig addUsernamePasswordProvider:self];
[commonConfig configureManager:httpConvManager];
Extra Notes:
In your code sample, you're using the MAFLogonCore API's directly, for creating the application registration. This is ok, but you should notice that you're setting username/password already, here:
regContext.backendUserName = self.usernameInput.text;
regContext.backendPassword = self.passwordInput.text;
I think that the session cookie returned by completing this operation should carry over to any subsequent data requests made via HttpConversationManager. As a result, you might not see the UsernamePasswordProvider be invoked in the session after you register (this is a one-time operation, only during the end-user's first session), but only in future sessions.
Hi Stan,
thank you very much for the very good explanations and the corrections of my code.
Now I changed the code to the line below and yes I use the MAFLogonCore API's directly, for creating the application registration. So it is true that the UsernamePasswordProvider is not invoked in the session after my registration.
If I do the initial register and close the app and open it again the provideUsernamePasswordForAuthChallenge is called and i can create the credential. If the user or password is wrong I get the return message with a error and I can handle this event. Every thing perfect.
If I close the Application upon making open the onlineStore and open the app again the online store open successfully without to call the provideUsernamePasswordForAuthChallenge. Is the reason the session cookie or the onlineStore him self? Who did save the credentials? Do it come from the regContext again?
It is possible that I can call provideUsernamePasswordForAuthChallenge every time the onlineStore will open? I use the persistence:NSURLCredentialPersistenceNone. The persistence:NSURLCredentialPersistenceForSession would be good to but if the user close the app the session should be closed also. So that I can check the user credentials again.
Here the new code:
- (IBAction)openStore:(id)sender {
NSError *error = nil;
HttpConversationManager* httpConvManager = [[HttpConversationManager alloc] init];
CommonAuthenticationConfigurator* commonConfig = [[CommonAuthenticationConfigurator alloc] init];
[commonConfig addUsernamePasswordProvider:self];
[commonConfig configureManager:httpConvManager];
//open MAFContext to get the ebdpointURL
MAFLogonCore *lc = self.appDelegate.lc;
MAFLogonContext *context;
if ([lc unlockSecureStore:@"abcd1234" error:&error]) {
context = [lc getContext:&error];
}
MAFLogonRegistrationContext *regContext = context.registrationContext;
SODataOnlineStore *onlineStore = [[SODataOnlineStore alloc]
initWithURL:[NSURL URLWithString:regContext.applicationEndpointURL]
httpConversationManager:httpConvManager];
[onlineStore setOnlineStoreDelegate:self];
[onlineStore openStoreWithError:&error];
}
#pragma mark - UsernamePasswordProviderProtocol Delegates
-(void)provideUsernamePasswordForAuthChallenge:(NSURLAuthenticationChallenge *)authChallenge completionBlock:(void (^)(NSURLCredential *, NSError *))completionBlock {
NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:self.userNameBackend
password:self.userPasswordBackend
persistence:NSURLCredentialPersistenceForSession];
if (!authChallenge.previousFailureCount) {
completionBlock(credential, nil);
}
else {
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:@"Userdata Error" forKey:NSLocalizedDescriptionKey];
NSURLResponse *response = authChallenge.failureResponse;
NSError* error = [NSError errorWithDomain:response.URL.description code:401 userInfo:errorDetail];
completionBlock(nil, error);
}
}
Hi Andre, I think it is most likely the cookie is still stored. You can verify this by adding this code to the application, early in the the application before trying to open the store (I might try AppDelegate:applicationDidBecomeActive).
NSLog(@"cookies on in the jar = %@", [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies);
Note: the X-SMP-APPCID cookie should be retained--we use this for a variety of things like log settings and E2E trace document upload.
Hi Stan,
thank you for your help. Now I think I understand the request response challenge much better.
In my case I check the password before we open the online store with teh changePAssword MAFContext-Feature:
- (void)testPassword {
NSError *error = nil;
MAFLogonCore *lc = self.appDelegate.lc;
lc.logonCoreDelegate = self;
MAFLogonContext *context;
if ([lc unlockSecureStore:@"abcd1234" error:&error]) {
context = [lc getContext:&error];
}
[lc changeBackendPassword:self.userPasswordBackend];
}
The MAFLogon test the new (or old) password and with the MAFLogonCoreDelegate:
-(void) changePasswordFinished:(NSError*)anError
we can handle the error or can then open the store.
Thanks again that you take the time to help me.
André
That's an innovative solution. I like it!
Best,
Stan