If I have a server where I authenticate with username/password and get auth token for subsequent requests, what would be the best approach addressing this problem?
The flow should be like this:
- Start request
- If we don't have auth token - get it with username and password
- Make request with auth token
- If request failed because token expired, get new auth token with user name and password
- Retry request with new auth token
- Finish
I've noticed that Volley already might have something that might solve this issue - Authenticator https://android.googlesource.com/platform/frameworks/support/+/4474bc11f64b2b274ca6db5a1e23e8c1d143d5fa/volley/src/com/android/volley/toolbox/Authenticator.java It contains getAuthToken() and invalidateAuthToken() methods which would be exactly what I want. But it seems that it's never used in the library at all.
I used volley for an authentication system using longlive (LLT) and shortlive (SLT) tokens.
I did it manually but it really wasn't much work once you get it all laid out.
Have all secure requests subclass a baseSecureRequest that can handle this token mechanism common to all secure request in its onResponse() and onErrorResponse().
It becomes a little node.js style, where requests send other requests and await callbacks.
An app may have a dozen screens, with only half requiring auth access - so each screen should be ignorant as to the requirements of its request.
Scenario A
We attempt to send a secure request. We notice we don't have a SLT in
memory, so make a TokenRequest.
TokenRequest's onResponse() saves
that token to memory (let a singleton session manager hold onto it or
similar omni-present class)
Now callback to the original
concrete-class request object to continue with the newly updated token.
Scenario B
We send a secure request but our SLT is stale (expired)
The server returns an error code or msg that you can catch in the
general onErrorResponse() of your baseSecureRequest.
In this onError(), you send a refreshTokenRequest() object that
attempts to refresh the SLT in memory by requesting a new SLT from the server using the LLT.
the onResponse() of the refreshTokenRequest can now callback to the
original request to resend.
however the onErrorResponse() should probably abandon the entire thing because chances are
anything that isn't a connectivity error - is an error caused by
invalid LLT. If you keep trying to refresh with a bad LLT you will never get out.
You might want to use the AccountManager API in Android for authentication and authorization. You may follow the blog here.
For OAuth 2.0 server side implementation you may read the IETF draft V2-31 here.
For better understanding of OAuth 2.0 you may read the blog by Nitesh Kumar here.
For Server side implementation of OAuth 2.0 you can fork Apis Autherization Server repo at Github.
More implementation option can be found at the website of OAuth 2.0 here.
Did you see this blog post? https://smaspe.github.io/2013/06/06/volley-part2.html
Demonstrates a simple way of overriding request object to use twitter tokens.
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
String auth = "Basic "
+ Base64.encodeToString((TwitterValues.CONSUMER_KEY
+ ":" + TwitterValues.CONSUMER_SECRET).getBytes(),
Base64.NO_WRAP);
headers.put("Authorization", auth);
return headers;
}
In my case, I changed a "Basic" authentication to a Token authentication as follows:
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String,String> headers = new HashMap<>();
//todo Esta es una authenticación basica (usuario y contraseña)
/*String credentials = USER+":"+PASSWORD;
String auth = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
headers.put("Authorization", auth);*/
//todo Esta es una authenticación con token (por tiempos)
headers.put("Authorization", "Bearer" + " " + "tokenString");//App.getToken()
return headers;
}
What I did was to save the Login in a global static variable and then be able to use it.
getToken() failed. Status BAD_AUTHENTICATION error
I also faced the same problem.
Solution: check whether the device is sign-in with your Google account.
Related
I understand the flow to use OAuth2 is:
after the short-lived access token expires (server returning 401), the client has to request a new one using the refresh token.
To implement it in an iOS (with AFNetworking) or Android (with Volley) app, I imagine the network manager has to be able to detect returned 401 error and then send request to the auth server.
The problem is with the concurrent usage of the network. Consider the scenario where the access has expired, the app sends 2 requests: req1 and after 100ms, req2. Drawn on a timeline, this looks like:
req1 --> 401 --> (refresh req) --> OK, new access and fresh tokens --> retry req1
req2 --> 401 --> (refresh req) --> 403, wrong refresh token
The final result is req2 will fail and the app logs user out because of the 403 error.
So my questions are
is this implementation heading towards a right direction? Or it's wrong to refresh after receiving 401? Should I instead refresh the token when the user starts the app (at the cost of slowing down app launch)
How can I solve the concurrency issue?
Since you have an existing token manager, I would add some extra logic into it (in Java):
class TokenManager {
private String accessToken;
private CompletableFuture<String> accessTokenRefreshComletableFuture;
public CompletableFuture<String> getAccessToken() {
if (this.accessToken is expired) {
// If refreshed accessToken is being requested
CompletableFuture<String> runningRequestFuture = this.accessTokenRefreshComletableFuture;
if (runningRequestFuture == null) {
// For thread safety, this assignment should be synchronized (or made atomic)
// with the previous reading
this.accessTokenRefreshComletableFuture = new CompletableFuture<>();
// Request a fresh access token.
// When you get a new access token, set the this.accessTokenRefreshComletableFuture
// complete and remove its reference from the manager class.
}
return runningRequestFuture;
}
// Synchronous result
return CompletableFuture.completedFuture(this.accessToken);
}
}
The manager doesn't return an access token, but a CompletableFuture (Promise in JavaScript - asynchronous result). If the access token needs to be refreshed, check first whether the /token endpoint request is already running. If it is, return it's CompletableFuture.
This way, you would always have either a valid access token or a single CompletableFuture waiting for a new access token.
I am currently working on creating a custom authentication for a Xamarin.Android app using Azure. I have successfully created my API and it is properly returning values when submitting a raw payload using Advanced REST Client.
I am now trying to implement this on Xamarin.Android using Azure's MobileServiceClient SDK and when using the invokeApi method as demonstrated below in my code, I am getting an exception indicating that it is calling GET instead of POST. Is anyone aware of what I might be doing wrong?
ex.Message returns
"Cannot GET /api/register?username=azureAccountTest&password=testingpassword"
public async Task RegisterAsync()
{
Dictionary<string, string> user = new Dictionary<string, string>
{
{ "username", username },
{ "password", password }
};
try
{
CancellationToken ct;
var result = await client.InvokeApiAsync("register", HttpMethod.Post, user, ct);
}
catch(Exception ex)
{
var message = ex.Message;
}
}
According to your description, I tested this issue on my local side and I could retrieve the authenticationToken as follows:
You used the following method for InvokeApiAsync:
public Task<JToken> InvokeApiAsync(string apiName, HttpMethod method, IDictionary<string, string> parameters, CancellationToken cancellationToken = default(CancellationToken));
Note: It summarizes that the Additional data will sent to through the query string.
Per my understanding, you could refer to the following method for sending additional data though the HTTP content as follows:
JObject user = new JObject();
user.Add("username", "bruce");
user.Add("password", "123456");
var result = await App.MobileService.InvokeApiAsync("/.auth/login/custom", HttpMethod.Post, user, ct);
Additionally, you need to specific the mobileAppUri with https endpoint when deploy to azure side. Here is a similar issue, you could refer to here. Moreover, I would recommend you refer to adrian hall's book about Custom Authentication.
UPDATE:
Based on your comment, I checked the custom authentication and found the following note from adrian hall's book about Custom Authentication:
You must turn on Authentication / Authorization in your App Service. Set the Action to take when request is not authenticated to Allow Request (no action) and do not configure any of the supported authentication providers.
My project for an Android app uses Checkmarx to scan the source code for security issues. Checkmarx reports an issue with the Google Volley library. Here is the error description:
Method performRequest at line 89 of
\app\libraries\volley\src\main\java\com\android\volley\toolbox\HurlStack.java
gets user input for the getHeaders element. This element’s value then
flows through the code without being properly sanitized or validated
and is eventually displayed to the user in method parseNetworkResponse
at line 61 of
\app\libraries\volley\src\main\java\com\android\volley\toolbox\JsonArrayRequest.java.
This may enable a Cross-Site-Scripting attack.
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
....
}
It says the headers of the request are cached and later displayed to the users in the JsonArrayRequest::parseNetworkResponse. However I can not find it is displayed to the user at all. Is this a false alarm? And in what condition a Cross-Site-Scripting attack may occur on an Android app? Does that only happen when you use a webview?
Let me answer your last question first. Yes, almost only WebViews are affected by it. Another (less common) scenario can be if an XSS payload is saved into a file and later opened by a browser.
HTTP headers are considered valid vector for XSS, so it doesn't seem like a false alarm. Here are two resources for further reading:
Cross-Site Scripting in HTTP Headers
CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
I have an android app that have a login form for student, and I want to check the student credential at web api depending on the stored data in sql server
I have searched the web and watch many videos that talking about many scenarios and nothing helped me.
All I want is a custom validation for my rest service (so I should send the credential for each request)
What should I do at asp.net web api service
how I can implement that at android application
Seems you didn't search for "Web API Token Based Authentication" ;) Anyhow what you need to implement is very simple.
You need to use OAuth 2.0 Resource Owner Credentials Flow which means that you want to provide the username/password only once for a specific endpoint i.e(/token) and then you if the username/password valid you obtain something called Bearer Access Token.
This token is valid for specified period and you can configure this in your Web API.
Once you obtain the access token, you need to store it securely in your android app, then you keep sending it with each request to your web api protected end points using the Authorization header (Bearer scheme(.
I've written very detailed post which covers your scenario 100%. Please check the post Token Based Authentication and let me know if you need further help.
I have used basic authentication for security,so I should provide the base64 encoding of
username:password
in header for each request as the following
authorization: Basic 'encoded username:password
httpGet.setHeader("Authorization", "Basic "+encodeUsernameAndPassword());
At the server side I have implemented message handler
public class BasicAuthenticationHandler : DelegatingHandler
{
public readonly IAuthenticationService authService;
public BasicAuthenticationHandler(IAuthenticationService service)
{
this.authService = service;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader == null || authHeader.Scheme != "Basic")
{
return Unauthorized(request);
}
string encodedCredentials = authHeader.Parameter;
var credentialsBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialsBytes).Split(':');
if (!authService.Authenticate(credentials[0], credentials[1]))
{
return Unauthorized(request);
}
string[] roles = null;//todo
IIdentity identity = new GenericIdentity(credentials[0], "Basic");
IPrincipal user = new GenericPrincipal(identity, roles);
HttpContext.Current.User = user;
return base.SendAsync(request, cancellationToken);
}
in a nutshell:
Can I work with the Google Play Android Developer API from server-side without providing any app in the play store?
Background:
I'm working on a project which provides apps with monthly subscriptions. The coresponding data of each subscription (purchase token, date etc) gets stored in the backend database.
Now I want to create a cronjob that iterates through each of these datasets.And for each subscription I'd like to contact the Google API to retrieve the information if the subscription is still valid or not, and update our database corresponding to the responsed status.
For the backend logic I use the google-api-java-client library.
To either cancel or verify subscriptions I need to authenticate myself with OAuth2 before.
Been there, done that.
new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes("https://www.googleapis.com/auth/androidpublisher") // $1
.setServiceAccountPrivateKeyFromP12File(new File(filePath))
.setClientSecrets(CLIENT_ID, CLIENT_SECRET) // $2
.build();
$1: I don't know if the given account scope is valid. Because I just could find this value in a very few examples, but neither in this overview nor in the google playground
$2 I guess this is necessary, even though I found a lot of example which did not provide this information.
But, unfortunately, I can't see any differences when I provide invalid data (like wrong email or private key).
Questions
How can i verify that the GoogleCredential is correct?
May I just see it in the next steps, like contacting ie the androidpublisher API?
In the next step I try to get purchase status of a subscription:
Androidpublisher publisher = new Androidpublisher.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(GOOGLE_PRODUCT_NAME) // $1
.build();
Androidpublisher.Purchases purchases = publisher.purchases();
Androidpublisher.Purchases.Get get = purchases.get("android.test.purchased", "monthly001", "mytoken"); // $2
SubscriptionPurchase subscripcion = get.execute();
$1: My dummy product name from the API console -> API Access
$2: Beside the fact, that the androidpush API does not allow contacting it via service accounts, but only via web server applications auth flow, I don't have any clue what to insert in the parameter of the get- method.
Here's the API:
https://developers.google.com/android-publisher/v1/purchases/get
Questions
What is the package name and what is the subscriptionId in this context?
Where do I get/set these values?
After reading this document I know there is a way to to deal with fake/static responses. But I can't read anywhere if this is also possible for subscriptions, or just for in-app-billings on mobile devices only.
I'm wondering anyway why/if there is any easy way of developing with a sandbox or s.th. simliar.
I still have the feeling that I'm just missing a big part to understand how the things should work.
Maybe someone of you can give me a hint how to proceed at this place or may say me where i'm wrong.
Kind regards,
Christopher
I could now figure out most of my previous understanding problems.
=1= GENERATE AUTHORIZATION URL
String authorizeUrl = new GoogleAuthorizationCodeRequestUrl(googleClientId,callbackUrl,"https://www.googleapis.com/auth/androidpublisher").build()
// See why: http://stackoverflow.com/questions/8433990/when-authenticating-with-oauth-and-youtube-always-get-error-invalid-grant-on
authorizeUrl += "&approval_prompt=force&access_type=offline"
=2= AUTHENTICATE
Since the server-webflow is not working for the androidpublisher API the customer must now call the URL generated in (1) manually.
=3= CALLBACK
The google callback should process the next steps. The callback contains the parameter "code" which we have to use.
=4= REQUEST AUTH-TOKEN
// Build the HTTP parameter
Map<String,String> params = [:]
params.put("grant_type", "authorization_code")
params.put("code", code.encodeAsURL())
params.put("client_id", customer.googleClientId.encodeAsURL())
params.put("client_secret", customer.googleClientSecret.encodeAsURL())
params.put("redirect_uri", getCallbackUrl().encodeAsURL())
// Send the POST request
// This action might throw an exception in case any parameter were wrong, invalid or not specified.
String result = HttpRequestHandler.sendRequest("https://accounts.google.com/o/oauth2/token", params);
JSONElement jsonResult = JSON.parse(result)
// Map result
OAuth2Result oAuth2Result = new OAuth2Result()
oAuth2Result.accessToken = jsonResult.getAt("access_token")
oAuth2Result.refreshToken = jsonResult.getAt("refresh_token")
oAuth2Result.ttlSeconds = Integer.parseInt(jsonResult.getAt("expires_in").toString())
oAuth2Result.tokenType = jsonResult.getAt("token_type")
=5= REQUEST REFRESH TOKEN
// Build the HTTP parameter
Map<String,String> params = [:]
params.put("grant_type", "refresh_token")
params.put("refresh_token", this.customer.googleRefreshToken.encodeAsURL())
params.put("client_id", customer.googleClientId.encodeAsURL())
params.put("client_secret", customer.googleClientSecret.encodeAsURL())
// Send the POST request
// This action might throw an exception in case any parameter were wrong, invalid or not specified.
String result = HttpRequestHandler.sendRequest("https://accounts.google.com/o/oauth2/token", params);
JSONElement jsonResult = JSON.parse(result)
// Map result
OAuth2Result oAuth2Result = new OAuth2Result()
oAuth2Result.accessToken = jsonResult.getAt("access_token")
oAuth2Result.refreshToken = jsonResult.getAt("refresh_token")
oAuth2Result.ttlSeconds = Integer.parseInt(jsonResult.getAt("expires_in").toString())
oAuth2Result.tokenType = jsonResult.getAt("token_type")