I'm making an iOS app, and I'm using an UIWebView to show a site, and everything seems to be working fine.
But when I go to a page of this site that require HTTP Authentication (Basic/NTLM Auth, HTTP) it does not work fine.
I was reading and I found a method didReceiveAuthenticationChallenge that indicate when is necessary Authentication in a page.
I found a example provided by Apple (but it does not work for me). https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html
-(void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential = [NSURLCredential credentialWithUser:[self preferencesName]
password:[self preferencesPassword]
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential
forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
// inform the user that the user name and password
// in the preferences are incorrect
[self showPreferencesCredentialsAreIncorrectPanel:self];
}
}
I was doing the same testing In Android and I found that We can use this method (It works for me):
#Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
handler.proceed(username, password);
...
}
My question is, What is the correctly way to do HTTP Authentication (Basic Auth, HTTP) in objective-C ?
I did a test with two apps(android and IOS) for the same WebSite so Android app works fine, but the iOS app not.
Any advice will be use full for me!
Thanks.
Related
I have a Xamarin.Forms 5.0 application (for both iOS and Android). I am not sure if this is a Xamarin issue or just Android. On iOS it is working fine.
In the app I use a webview to display single page webapplication. Inside that spa I have a login page with a "remember me" checkbox. When this is checked, the backend creates persistent cookie instead of a sessioncookie. The login is done with a XHR request to the backend.
Seems all working fine, but when the app is restarted, it doesn't know the cookie anymore and the user has to login again.
When I do a full reload of the page in the webview (after login), it looks like the cookie is persisted. After restarting the app, the user is logged in automatically, so the cookie is available.
So the problem seems to be that new cookies in the response of XHR requests are not persisted, while the response cookies of a normal page request are.
Anybody any ideas about this?
Added some code
I created a simple Xamarin.Forms project with an Android app.
Added this in the MainPage.xaml:
<StackLayout>
<Button Clicked="Button_Clicked" Text="(Re)Load"></Button>
<WebView x:Name="wv"
WidthRequest="1000"
HeightRequest="1000"
></WebView>
</StackLayout>
And in the codebehind:
private void Button_Clicked(object sender, EventArgs e)
{
wv.Source = "https://---.---.nl/portal";
}
This loads a SPA webapplication. Not much code to show.
As i know, UWP and iOS would share their cookie containers automatically between the WebView and native http client, however Android does not.
The WebView could use CookieManager to handle cookies. If you want to share cookies between the two, you will have to manually process the information. You need to know the Uri of the domain for these cookies.
private void CopyCookies(HttpResponseMessage result, Uri uri)
{
foreach (var header in result.Headers)
if (header.Key.ToLower() == "set-cookie")
foreach (var value in header.Value)
_cookieManager.SetCookie($"{uri.Scheme}://{uri.Host}", value);
foreach (var cookie in GetAllCookies(_cookieContainer))
_cookieManager.SetCookie(cookie.Domain, cookie.ToString());
}
public void ReverseCopyCookies(Uri uri)
{
var cookie = _cookieManager.GetCookie($"{uri.Scheme}://{uri.Host}");
if (!string.IsNullOrEmpty(cookie))
_cookieContainer.SetCookies(new Uri($"{uri.Scheme}://{uri.Host}"), cookie);
}
It is not very clear, at least for me, when the WebView flushes the cookies and when not.
The simple solution for me was to flush the cookies everytime the onPause lifecycle event of the activity containing the webview is called. This occurs when another activity is started, or the user switches to another app or closes the app.
protected override void OnPause()
{
base.OnPause();
Android.Webkit.CookieManager.Instance.Flush();
}
I am building an Android App which communicates with my REST API that is protected by Spring Security.
Since the Android App is "public" and no keys etc is secure I want to create diffrent obstacles and make things complicated to protect my API as much as possible.
One way in which I would like to add more security is to make sure that the one calling my API has a certificate. I don't want to create thousands of certificates in my APIs trust-store so I just want to make sure that the caller have one single certificate that I hid away in a keystore in my Android app.
In the examples I have found it seems like a "normal" X509Certificate authentication in Spring Security requires a unique certificate for every user and then this certificate replaces Basic auth or JWT auth. I would like to have individual client JWT tokens but make sure that every call brings my ONE Android App certificate to make (more) sure that someone is calling my API from my Android app.
Is this possible or is it just not what it is for?
When you create a RestTemplate you can configure it with a keystore and trust-store so in that end it should be easy. But as for protecting my REST API it seems more difficult since I want both certificate + JWT token or Basic auth.
I am not using XML configuration for my securityconfig. I instead extend WebSecurityConfigurerAdapter. It would be great if this was configurable in the configure(HttpSecurity http) method, but I'm thinking that maybe I could achieve this in a OncePerRequestFilter somehow? Perhaps configure a filter before my JwtAuthFilter?
Edit:
In all the examples I have found for configuration of spring security they always seems to use the certificate as an authentication. I just want to configure so that when someone call example.com/api/** it checks so that the certificate is approved by my custom trust store (so that I "know" it is probably a call from my app) but if someone call example.com/website it should use the default java trust store.
If someone call example.com/api/** I would like my server to
check certificate and kill the connection if the certificate is not approved in my custom truststore.
If certificate is ok, establish https (or move on if I can't kill the connection before it have already established https-connection) to user auth with Basic-/JWT-authentication.
I think I figured it out. Here is how I configured it and it seems to work.
The "/**" endpoint is the website which should work with any browser without any specific certificate, but it requires Admin authority (you need to login as admin).
The "/api/**" and "/connect/**" endpoints require the correct certificate, the correct API-key and valid Basic- or JWT-token authentification.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/authenticateTheUser")
.permitAll()
.and()
.logout()
.permitAll().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http.requestMatchers()
.antMatchers("/connect/**","/api/**")
.and()
.addFilterBefore(new APIKeyFilter(null), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthorizationFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/connect/**").hasAnyRole("MASTER,APPCALLER,NEGOTIATOR,MEMBER")
.antMatchers("/api/**").hasAnyRole("MASTER,MEMBER,ANONYMOUS");
}
The ApiKeyFilter class is the one that check the api-key and also make sure that the certificate used in the call is approved in my server trust-store. The api-key check is all that I had to configure, the extended X509AuthenticationFilter will automatically check the request certificate. My ApiKeyFilter looks like this:
public class APIKeyFilter extends X509AuthenticationFilter {
private String principalRequestHeader = "x-api-key";
private String apiKey = "XXXX";
public APIKeyFilter(String principalRequestHeader) {
if (principalRequestHeader != null) {
this.principalRequestHeader = principalRequestHeader;
}
setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(authentication.getPrincipal() == null) {
throw new BadCredentialsException("Access Denied.");
}
String rApiKey = (String) authentication.getPrincipal();
if (authentication.getPrincipal() != null && apiKey.equals(rApiKey)) {
return authentication;
} else {
throw new BadCredentialsException("Access Denied.");
}
}
});
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certificates != null && certificates.length > 0) {
return certificates[0].getSubjectDN();
}
return super.getPreAuthenticatedCredentials(request);
}
}
Cred goes to these resources that helped me put things together:
Spring Boot - require api key AND x509, but not for all endpoints
spring security http antMatcher with multiple paths
I've successfully created a demo in Android using keycloak openid-connect protocol configuration for SSO. Now I want to do with SAML protocol.
Details I used in openid-connect:
client_id
username
password
grant_type
client_secret
Now, when I changed from openid-connect to SAML inside keycloak dashboard, so client-secreted option got invisible.
So, in android I removed that variable, and also changed in URL from openid-connect to SAML. But getting error that Page not found
I seen lot of example, searched and imported github project as well, but wither I'll get demo with openid-connect or I'll get demo without using keyclaok.
I don't understand what else is required.
BTW, I followed this example for openid-connect and it is working as well: https://github.com/thomasdarimont/android-openid-connect/tree/feature/keycloak-oidc-demo
I'll share a bit code:
protected Boolean doInBackground(String... args) {
String authToken = args[0];
IdTokenResponse response;
showLog("Requesting ID token.");
try {
response = OIDCUtils.requestTokens(Config.tokenServerUrl,
Config.redirectUrl,
Config.clientId,
authToken);
} catch (IOException e) {
Log.e(TAG, "Could not get response.");
e.printStackTrace();
return false;
}
if (isNewAccount) {
createAccount(response);
} else {
setTokens(response);
}
return true;
}
Have a look, and there are really less examples on this things. Don't know why!
SAML is primarily for Browser Based Authentication (including Auth-Requests, Redirects, ...). So that's not really suitable for Android apps.
Do you have a good reason to use SAML and not stick to your (already working) OIDC solution?
I have angualrJS web app that recieves alerts from mobile app and marks the location of the mobile app user on google map. Once that mark is clicked info windo opens with name of user etc.
BackEnd is java maven project, and using spring boot.
My question is:
When I add spring boot security, to authenticate the page this stops all communication and no alerts show at all....
Any suggestions:
At the moment this is the application.js
var app = angular.module('sos',['ngRoute','mgcrea.ngStrap']);
app.config(['$httpProvider','$logProvider','$routeProvider',
function($httpProvider,$logProvider,$routeProvider) {
$logProvider.debugEnabled(true);
$routeProvider
.when('/', {
controller: 'MainController',
templateUrl: 'index.html',
controllerAs: 'controller'
})
.when('/download',{
templateUrl: 'download.html'
})
There is also a websocket connection that also stops and shows error 500 when I enable spring security...
This is web socket configuration:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(geoLocationHandler(),
"/GeoLocationHandler").setAllowedOrigins("*");
}
#Bean
public GeoLocationHandler geoLocationHandler(){
return new GeoLocationHandler();
}
Any suggestions why spring security stops the alerts and markers showing on the map on the web app? And the spring security would make websocket fail giving error 500?
I tried to extend WebSecurityConfigAdapter and add mathcers to home page etc didnt work, I tried cors filters didnt work, I tried csrf filters didnt work as well...Any suggestion would be appreciated....
Can u try this in your
WebSecurityConfig.java file?
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().httpBasic().and().authorizeRequests()
.antMatchers(PUBLIC_MATCHERS).permitAll().anyRequest().authenticated();
(my snippet , try it and then remove what you don't like)
I think that eventually you don't let anyone to GET.
Here I give you also my Global method , in case you need it.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
Ok, after research I found that I needed to include http method get to be able to recive alerts on the the web app, so my websecurityConfiguration is like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
//.and()
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/*").permitAll()
.antMatchers(HttpMethod.GET, "/api/geolocation/*").permitAll()
.antMatchers(HttpMethod.GET, "/GeoLocationHandler").permitAll()
.antMatchers(HttpMethod.GET, "/api/responser/*").permitAll()
.antMatchers(HttpMethod.GET, "/api/user/").authenticated()
.anyRequest().authenticated();
}
Otherwise the webapp will not accept any alerts....
Hope this helps someone needing it....
I'm currently new into the AppEngine world, and wanting to create a backend using Cloud Endpoints for a mobile application that I'm developing.
One of my problem right now is about the user's authentication. I've been following the Udacity's MOOC on App Engine, and they taught us how to authenticate the user for API request using a Google Accounts. On the backend side, we simply have to add a User parameter to our method, and check if the user is signed in. As far as I know, this user parameter is generated by App Engine, based on the Authorization header of our request. (might need some confirmation there)
Now, there's a bunch of stuff I'm not sure to understand and that weren't that well explained on this MOOC.
Now, I'd like to know if this is compatible with other OAuth schemes, beside Google? So, if I want to implement Facebook authentication, will I simply pass the facebook access token?
From what I searched, using the Facebook SDK on Android would lead me to be able to generate a User Access Token, which identifies my user to facebook. After sending it to my backend, I would want to check it's validity with Facebook, and if it's valid, create a new user to my application. Now, I'd also want to generate a new token that identify the user to my app. What would I need to do to do so?
You can supply your own authenticator to Endpoints and the injected User will be obtained with your authenticator
https://developers.google.com/appengine/docs/java/endpoints/javadoc/com/google/api/server/spi/config/Authenticator.html.
The Facebook credentials can be sent via a header, e.g. Authorization header and it can be accessed from backend via HttpServletRequest, which you can handle inside Authenticator.authenticate method.
For example.
// Custom Authenticator class
public class MyAuthenticator implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null) {
String user = authenticateFacebook(token); // apply your Facebook auth.
if (user != null) {
return new User(user);
}
}
return null;
}
}
// Endpoints class.
#Api(name = "example", authenticators = {MyAuthenticator.class})
public class MyEndpoints {
public Container getThing(User user) {
Container c = new Container();
if (user != null) {
c.email = user.getEmail();
}
return c;
}
public class Container {
public String email;
public String extraData;
}
}
When I try your example I always get an: java.lang.NullPointerException: authDomain must be specified. But I cannot set an authDomain on the User object. Any ideas?
UPDATE: This is connected to this Bug https://code.google.com/p/googleappengine/issues/detail?id=12060&q=endpoints&colspec=ID%20Type%20Component%20Status%20Stars%20Summary%20Language%20Priority%20Owner%20Log
in version 1.9.22