Autenticação de formulários Asp.Net ao usar o iPhone UIWebView

Estou escrevendo um aplicativo Asp.net MVC 2 que usa autenticação de formulários e atualmente estou tendo um problema com o nosso aplicativo do iPhone no que diz respeito à autenticação / login na web. Nós desenvolvemos um aplicativo simples para iPhone que usa o controle UIWebView. Nesta fase, tudo o que o aplicativo faz é navegar para o nosso site Asp.Net. Simples, certo? O problema é que o usuário não pode passar da página de login. Os passos da reprodução são:

  • Abra o aplicativo para iPhone.
  • O aplicativo navega para a página inicial.
  • o usuário não é autenticado, então eles são redirecionados para a canvas de login / página
  • O usuário digita o nome de usuário e a senha corretos. clica em enviar.
  • no lado do servidor, o usuário é autenticado e um cookie é gerado e enviado para o cliente usando FormsAuthentication.GetAuthCookie.
  • Os envios do servidor são redirecionados para enviar o usuário para a home page correta.

Mas o usuário é redirecionado de volta para a canvas de login!

Eu fiz uma extensa debugging sobre isso e o que eu sei é:

O cookie está sendo enviado para o cliente e o cliente está armazenando o cookie. Verificado isso no depurador do iPhone e também usando Javsascript para exibir dados de cookie na página. O cookie está sendo enviado de volta ao servidor. Verificado isso no depurador do Visual Studio. É o cookie correto (é o mesmo que foi definido). A propriedade User.Identity.IsAuthenticated retorna false por algum motivo, mesmo que o cookie de autenticação esteja contido no object Request. Verifiquei que o aplicativo do iPhone está configurado para aceitar cookies e eles estão no cliente.

Aqui está o engraçado: Funciona bem se você abrir o navegador Safari no iPhone e acessar diretamente o site.

Ele tem o mesmo comportamento no iPad, pois não passa da canvas de login. Esta reprocha nos emuladores e nos dispositivos.

Este mesmo site foi testado com o IE 7-8, Safari (para Windows), Blackberry, IEMobile 6.5, Phone 7 e funciona. A única circunstância em que não funciona é o UIWebView no aplicativo para iPhone.

Eu tive exatamente o mesmo problema, mas com outro dispositivo (NokiaN8), e também rastreei o problema de volta para o User-Agent.

O IIS usa expressões regulares para corresponder à cadeia do User-Agent. A raiz do problema era que ele não tinha nenhuma expressão regular correspondente para o dispositivo específico e terminava em um dos níveis mais baixos de correspondência, em que as propriedades Default eram usadas. As propriedades padrão diziam que o navegador não suportava cookies.

Solução:

  1. Adicione uma pasta em seu projeto da Web chamado App_Browsers (clique com o botão direito do mouse no projeto, escolha: Add > Add ASP.NET Folder > App_Browsers ).
  2. Adicione um arquivo nessa pasta (clique com o botão direito do mouse, escolha: Add > New Item ). O arquivo pode ter qualquer nome, mas deve ter o .browser terminando.
  3. Adicione uma boa expressão correspondente e os resources corretos (ou adicione alterações ao Default ).

Dois exemplos:

            

Ou mude o padrão:

        

Mais informações: Esquema do Arquivo de Definição do Navegador

A solução encontrada foi criar um arquivo (generic.browser) e include este xml para informar ao servidor web que “Mozilla” e as configurações padrão do navegador devem suportar cookies.

      

Isso é corrigido no ASP.NET 4.5 e supõe-se que todos os navegadores suportem cookies, portanto, o arquivo .browser extra não será necessário.

Da pesquisa que fiz, o motivo pelo qual você não pode definir o User-Agent é que o UIWebView está definindo o valor do User-Agent antes de enviar a solicitação, ou seja, depois que você fez sua solicitação com seu código .

O truque para contornar este problema é usar algo chamado “método swizzling”, um conceito avançado e potencialmente perigoso de Objective-C que substitui um método padrão por um fornecido por você. O resultado final é que quando o seu pedido é enviado e o código do framework adiciona o User-Agent, ele será enganado usando o método que você forneceu.

O seguinte explica o que eu fiz para implementar isso, mas eu não sou um especialista em Objective-C e sugiro que você faça alguma pesquisa para se familiarizar com a técnica. Em particular, havia um link lá fora explicando melhor do que eu o que está acontecendo aqui, mas no momento não consigo encontrá-lo.

1) Adicione uma categoria no NSObject para permitir o swizzling.

 @interface NSObject (Swizzle) + (BOOL) swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector; @end @implementation NSObject (Swizzle) + (BOOL) swizzleMethod:(SEL) origSelector withMethod:(SEL)newSelector { Method origMethod= class_getInstanceMethod(self, origSelector); Method newMethod= class_getInstanceMethod(self, newSelector); if (origMethod && newMethod) { if (class_addMethod(self, origSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) { class_replaceMethod(self, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); } else { method_exchangeImplementations(origMethod, newMethod); } return YES; } return NO; } @end 

2) Subclass NSMutableURLRequest para permitir o swizzle:

 @interface NSMutableURLRequest (MyMutableURLRequest) + (void) setupUserAgentOverwrite; @end @implementation NSMutableURLRequest (MyMutableURLRequest) - (void) newSetValue:(NSString*)value forHTTPHeaderField:(NSString*)field { if ([field isEqualToString:@"User-Agent"]) { value = USER_AGENT; // ie, the value I want to use. } [self newSetValue:value forHTTPHeaderField:field]; } + (void) setupUserAgentOverwrite { [self swizzleMethod:@selector(setValue:forHTTPHeaderField:) withMethod:@selector(newSetValue:forHTTPHeaderField:)]; } @end 

3) Chame o método estático para trocar o método. Fiz esta chamada em didFinishLaunchingWithOptions:

 // Need to call this method so that User-Agent get updated correctly: [NSMutableURLRequest setupUserAgentOverwrite]; 

4) E depois usei assim. (O delegado de conexão salva os dados em uma matriz mutável e, em seguida, define manualmente o UIWebView usando seu método loadData quando terminar de carregar).

 - (void)loadWithURLString:(NSString*)urlString { NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; _connection = [NSURLConnection connectionWithRequest:request delegate:self]; [_connection start]; } 

Eu tive o mesmo problema exato, pesquisei e consolidei uma solução completa (de respostas acima e outros tópicos) aqui: http://www.bloggersworld.com/index.php/asp-net-forms-authentication-iphone-cookies/

  1. Você especificou um DestinationPageUrl na marcação?

  2. Você especificou o defaultURL no web.config?

Exemplo web.config

    

Exemplo DestinationPageUrl

   

Por último, você viu no cookie jar e viu se o seu cookie de session realmente existe?

Onde os cookies de uma UIWebView são armazenados?

A razão disso acontecer aparentemente tem a ver com o fato de que se o user-agent não for conhecido, o navegador assumirá que não aceita cookies (como outros responderam) e, em vez disso, o IIS colocará o valor ASPXAUTH na URL.

No entanto, o sistema de roteamento MVC aparentemente perdeu essa possibilidade, que é claramente um bug e, portanto, está ficando confuso.

Embora adicionar o .browser com um user-agent personalizado resolva o problema, ele não garante que outros user-agents também sejam resolvidos e, de fato, eu descobri que o K9 Browser para o android também tem esse problema e, como tal, é apenas uma solução se você tiver um sistema de registro como o elmeh para rastrear esses erros.

Por outro lado, adicionar um padrão traz a questão se é verdade que todos os navegadores aceitam cookies, o que aparentemente é o motivo pelo qual o IIS não assume isso.

No entanto, além de adicionar explicitamente os user agents, é possível adicionar no método global.asax RegiterRoutes () um manipulador explícito para ignorá-lo, da seguinte maneira:

  routes.MapRoute( "CookieLess", // Route name "(F({Cookie}))/{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); 

No entanto, neste caso, será necessário copiar todas as inputs de rota para corresponder à situação sem cookies, a menos que uma esteja prestes a escrever um manipulador de rota personalizado.

Ou podemos usar a rota sem cookie acima para enviar o usuário a uma página de erro explicando que o seu navegador não é suportado no momento e enviar um alerta para o webmaster com o user-agent para lidar com ele.