I'm developing Apple Authentication feature on Android with React Native, using this library: https://github.com/invertase/react-native-apple-authentication. Everything goes fine, but there is still a thing I want to show in the form is that the real email, or Apple ID of the user. The default settings of Apple accounts is that use private relay, so after I call signIn() method in this code fragment
// App.js
import { appleAuthAndroid } from '#invertase/react-native-apple-authentication';
import 'react-native-get-random-values';
import { v4 as uuid } from 'uuid'
async function onAppleButtonPress() {
// Generate secure, random values for state and nonce
const rawNonce = uuid();
const state = uuid();
// Configure the request
appleAuthAndroid.configure({
// The Service ID you registered with Apple
clientId: 'com.example.client-android',
// Return URL added to your Apple dev console. We intercept this redirect, but it must still match
// the URL you provided to Apple. It can be an empty route on your backend as it's never called.
redirectUri: 'https://example.com/auth/callback',
// The type of response requested - code, id_token, or both.
responseType: appleAuthAndroid.ResponseType.ALL,
// The amount of user information requested from Apple.
scope: appleAuthAndroid.Scope.ALL,
// Random nonce value that will be SHA256 hashed before sending to Apple.
nonce: rawNonce,
// Unique state value used to prevent CSRF attacks. A UUID will be generated if nothing is provided.
state,
});
// Open the browser window for user sign in
const response = await appleAuthAndroid.signIn();
// Send the authorization code to your backend for verification
}
I got an id_token, after I decode the token, I got an object in this pattern:
{
"aud":"",
"auth_time":,
"c_hash":"xxxxxxx",
"email":"xxxxxxx#privaterelay.appleid.com",
"email_verified":"true",
"exp":1663743691,
"iat":1663657291,
"is_private_email":"true",
"iss":"https://appleid.apple.com",
"nonce":"xxxxxxxxxxxxxxxxxx",
"nonce_supported":true,
"sub":"xxxxxxxxxxxxxxxx"
}
whose the email is not the real email that the user entered before. So this can cause a confusion after that when I show the user's information in a form to confirmation, I can only use this private relay email. I wonder that whether any way to decode this email to get the real one, by using c_hash for instance.
Related
When implementing Stripe in Android there is CardInputWidget which gives you a Card object, then you get a token from Stripe API using that card and finally you send that token to your server, which makes the charge.
When implementing Stripe in iOS I can see that the workflow is quite different. The server needs to have an API endpoint to provide Stripe ephemeral key. Is there any way to do it like in Android workflow - without ephemeral key?
let stripeCard = STPCardParams() /// Declare Stripe Payment Function
stripeCard.name = "Card Name" // you can enter Card Owner name which is displayed in card
stripeCard.number = "Card number" //You can enter card Number which is displayed in Card
stripeCard.expMonth = "ExpireMonth" // enter expire Month which is displayed in Card
stripeCard.expYear = "ExpireYear" // enter expire year which is displayed in Card
stripeCard.cvc = "CVV" // enter CVV which is displayed in Card
//after check card valid or not from below method
if STPCardValidator.validationState(forCard: self.stripeCard) == .valid
{
// the card is valid.
print("Valid card")
STPAPIClient.shared().createToken(withCard: self.stripeCard, completion: { (token, error) -> Void in
if error != nil {
print(error ?? "")
return
}
print(token!)
// call your php Api Pass token id which is given bellow link PHP API link
APICallResponse.shared.getStripePayment(arrUserLoginDetails: self.arrStripePayement,vc:self, completion: {data in
self.SkripePayment = data
})
}
}
You can take reference of PHP API from given link
Stripe payment with php
Yes, absolutely, you can develop with Stripe's iOS SDK without using their pre-built UI or ephemeral key method.
You can use your own form or the STPPaymentCardTextField class, create a STPCardParams instance, and then create a STPToken from that which you can send off to your backend.
STPCardParams *cardParams = [[STPCardParams alloc] init];
cardParams.number = #"4242424242424242";
cardParams.expMonth = 10;
cardParams.expYear = 2020;
cardParams.cvc = #"345";
[[STPAPIClient sharedClient] createTokenWithCard:cardParams completion:^(STPToken *token, NSError *error) {
...
}
}];
See https://stripe.com/docs/mobile/ios/custom#stpapiclient--stpcardparams for more.
The Account Kit documentation states that if your began the login session with AccountKitActivity.ResponseType.TOKEN, it's possible to access the Account Kit ID, phone number and email of the current account via a call to getCurrentAccount().
Is it possible to get the user's phone number if you began with AccountKitActivity.ResponseType.CODE just like the way Saavn does it?
Yes, it's possible provided you use LoginType.PHONE in your configuration.
AccountKit.getCurrentAccount(new AccountKitCallback<Account>() {
#Override
public void onSuccess(final Account account) {
String accountKitId = account.getId();
PhoneNumber phoneNumber = account.getPhoneNumber();
String phoneNumberString = phoneNumber.toString();
}
#Override
public void onError(final AccountKitError error) {
// Handle Error
}
});
This is your phone number: phoneNumberString; but, account.getEmail() will return null if LoginType.PHONE was used in your configuration.
Vice versa if you use LoginType.EMAIL in your configuration.
The purpose of using CODE instead of TOKEN is to shift the token request to your application server. The server uses the Graph api to submit the auth token and if the auth token is valid, the call returns the access token which is then used to verify the user's identity for subsequent API calls.
A graph call to validate the access token returns the account kit id plus additional metadata like the associated phone number and/or email.
{
"id":"12345",
"phone":{
"number":"+15551234567"
"country_prefix": "1",
"national_number": "5551234567"
}
}
You can fetch Account ID,Email and Phone number using below code:
let accountKit = AKFAccountKit(responseType: AKFResponseType.accessToken)
accountKit.requestAccount { (account, error) in
if(error != nil){
//error while fetching information
}else{
print("Account ID \(account?.accountID)")
if let email = account?.emailAddress,email.characters.count > 0{
print("Email\(email)")
}else if let phoneNum = account?.phoneNumber{
print("Phone Number\(phoneNum.stringRepresentation())")
}
}
}
If you have access code/token...
On server or client, you can exchange access token for mobile number and country code with this FB AccountKit API - https://graph.accountkit.com/v1.1/me/?access_token=xxxxxxxxxxxx. Here xxxxxxxxxx is your Access Token.
If you have auth code/token instead...
You can first exchange the access code for an access token on the server side (because it contains the App Secret) with this API - https://graph.accountkit.com/v1.1/access_token?grant_type=authorization_code&code=xxxxxxxxxx&access_token=AA|yyyyyyyyyy|zzzzzzzzzz. Here xxxxxxxxxx, yyyyyyyyyy and zzzzzzzzzz are the auth code, app id and app secret respectively. Once you have the access token with it, you can get the mobile number with the above mentioned API.
Good Luck.
I have a login/logout module in my django app (using DRF).
it works in token authentication - when user logs in he passes a username and a password and gets a token, that can be used forever.
(I save the token in my client app).
when he logs out - I delete the token from the client app.
The problem I noticed is that when one client (android app) in logged in with user1 for example (currently has the token that was achieved from the server), other clients can login as the same user (provide same username and password and get the token) - and now I have both clients logged in as user1.
Here is the django code for getting the token:
class ObtainAuthTokenAndUser(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
user_serializer = UserSerializer(user)
return Response({'token': token.key, 'user': user_serializer.data})
obtain_auth_token_and_user = ObtainAuthTokenAndUser.as_view()
What can I do in order to prevent this situation??
In the case of another client tries to log in with a already logged in user - I want to send a "already logged in from another device" message and a 401 HTTP.
Any ideas about how to approach this?
You can simply check if any valid token is already present in the database for the same user.
token, created = Token.objects.get_or_create(user=user)
instead of above method you should use
try:
token = Token.objects.get(user=user) // Chcck if token is present
except Token.DoesNotExist:
token = Token.objects.create(user=user) // Create new token, no token for this user
else:
return Response({'error': 'Already logged in', status=400})
I have been doing extensive research on how to authenticate your client (Android, iOS, web-app) with Cloud Endpoints without requiring your user to use their Google account login the way the documentation shows you.
The reason for this is that I want to secure my API or "lock it down" to only my specified clients. Sometimes I will have an app that does not have a user login. I would hate to pester my user to now sign in just so my API is secure. Or other times, I just want to manage my own users like on a website and not use Google+, Facebook, or whatever else login authentication.
To start, let me first show the way you can authenticate your Android app with your Cloud Endpoints API using the Google Accounts login as specified in the documentation. After that I will show you my findings and a potential area for a solution which I need help with.
(1) Specify the client IDs (clientIds) of apps authorized to make requests to your API backend and (2) add a User parameter to all exposed methods to be protected by authorization.
public class Constants {
public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com";
public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com";
public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
}
import com.google.api.server.spi.auth.common.User; //import for the User object
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
public class MyEndpoint {
/** A simple endpoint method that takes a name and says Hi back */
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
(3) In Android call the API method in an Asynctask making sure to pass in the credential variable in the Builder:
class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
private static MyApi myApiService = null;
private Context context;
#Override
protected String doInBackground(Pair<Context, String>... params) {
credential = GoogleAccountCredential.usingAudience(this,
"server:client_id:1-web-app.apps.googleusercontent.com");
credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
if(myApiService == null) { // Only do this once
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), credential)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
// end options for devappserver
myApiService = builder.build();
}
context = params[0].first;
String name = params[0].second;
try {
return myApiService.sayHi(name).execute().getData();
} catch (IOException e) {
return e.getMessage();
}
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(context, result, Toast.LENGTH_LONG).show();
}
}
What is happening is that in your Android app you are showing the Google account picker first, storing that Google account email in you shared preferences, and then later setting it as part of the GoogleAccountCredential object (more info on how to do that here).
The Google App Engine server receives your request and checks it. If the Android Client is one of the ones you specified in the #Api notation, then the server will inject the com.google.api.server.spi.auth.common.User object into your API method. It is now your responsibility to check if that User object is null or not inside your API method. If the User object is null, you should throw an exception in your method to prevent it from running. If you do not do this check, your API method will execute (a no-no if you are trying to restrict access to it).
You can get your ANDROID_CLIENT_ID by going to your Google Developers Console. There, you provide the package name of your Android App and the SHA1 which generates for you an android client id for you to use in your #Api annotation (or put it in a class Constants like specified above for usability).
I have done some extensive testing with all of the above and here is what I found:
If you specify a bogus or invalid Android clientId in your #Api annotation, the User object will be null in your API method. If you are doing a check for if (user == null) throw new UnauthorizedException("User is Not Valid"); then your API method will not run.
This is surprising because it appears there is some behind the scenes validation going on in Cloud Endpoints that check whether the Android ClientId is valid or not. If it is invalid, it won't return the User object - even if the end user logged in to their Google account and the GoogleAccountCredential was valid.
My question is, does anyone know how I can check for that type of ClientId validation on my own in my Cloud Endpoints methods? Could that information be passed around in an HttpHeader for example?
Another injected type in Cloud Endpoints is the javax.servlet.http.HttpServletRequest. You can get the request like this in your API method:
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name, HttpServletRequest req) throws UnauthorizedException {
String Auth = req.getHeader("Authorization");//always null based on my tests
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
But I am not sure if the necessary information is there or how to get it.
Certainly somewhere there must be some data that tells us if the Client is an authorized and specified one in the #Api clientIds.
This way, you could lock-down your API to your Android app (and potentially other clients) without ever having to pester your end users to log in (or just create your own simple username + password login).
For all of this to work though, you would have to pass in null in the third argument of your Builder like this:
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
Then in your API method extract whether or not the call came from an authenticated client, and either throw an exception or run whatever code you wanted to.
I know this is possible because when using a GoogleAccountCredential in the Builder, somehow Cloud Endpoints knows whether or not the call came from an authenticated client and then either injects its User object into the API method or not based on that.
Could that information be in the header or body somehow? If so, how can I get it out to later check if it is there or not in my API method?
Note: I read the other posts on this topic. They offer ways to pass in your own authentication token - which is fine - but your .apk will still not be secure if someone decompiles it. I think if my hypothesis works, you will be able to lock-down your Cloud Endpoints API to a client without any logins.
Custom Authentication for Google Cloud Endpoints (instead of OAuth2)
Authenticate my "app" to Google Cloud Endpoints not a "user"
Google Cloud Endpoints without Google Accounts
EDIT:
We used Gold Support for the Google Cloud Platform and have been talking back and forth with their support team for weeks. This is their final answer for us:
"Unfortunately, I haven't had any luck on this. I've asked around my
team, and checked all of the documentation. It looks like using OAuth2
is your only option. The reason is because the endpoint servers handle
the authentication before it reaches your app. This means you wouldn't
be able to develop your own authentication flow, and would get results
much like what you were seeing with the tokens.
I would be happy to submit a feature request for you. If you could
provide a little more information about why the OAuth2 flow doesn't
work for your customers, I can put the rest of the information
together and submit it to the product manager."
(frowny face) - however, maybe it is still possible?
I have implemented Endpoint Auth using a custom header "Authorization" and it works just fine. In my case this token is set after login but should work all the same with your app. Check your tests because the value should be there.
The way to retrieve that header is indeed:
String Auth = req.getHeader("Authorization");
You could take it a step further and define your own implementations of an Authenticator and apply it to your secure API calls.
So you don't have any user specific info, but just want to ensure that only your app is able to communicate with your backend...
This is what i think,
change
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
to
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.ANDROID_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
The Client ID is generated from the signature of your app. It can't be replicated. If you only allow your endpoints to accept requests from the Android App, your problem would be solved.
Tell me if this works.
Faced the same problem to find a solution to call my API safely from my endpoints, without using Google Account. We can't decompile an IOS App (Bundle), but decompile an Android App is so simple..
The solution I found is not perfect but do the job pretty good:
On android APP, I just create an constant String variable, named APIKey, with simply content (For example "helloworld145698")
Then I encrypt it with sha1, next md5, and finally sha1 (Order and frequency of encryption up to you) and store the variable on SharedPref (For Android) in private mode (Do this action on an random class in your App) It's this result encrypted I authorize on my Backend !
On my backend, I just add a parameter (named token for exemple) on every request
Example:
#ApiMethod(name = "sayHi")
public void sayHi(#Named("name") String name, #Named("Token") String token) {
if (token == tokenStoreOnAPIServer) {
//Allow it
} else {
//Refuse it and print error
}
}
On android, active ProGuard for obfuscated your code. It will be really unreadable for anyone who tried to decompile your app (Reverse engineering is really hardcore)
Not THE perfect secure solution, but it works, and it will be really really (really) difficult to find the real API key for anyone who try to read your code after decompilation.
I'm developing a Sinatra app for which I'd like to use OmniAuth. So far, I have something similar to this for the web app:
http://codebiff.com/omniauth-with-sinatra
I'd like the web app to be usable via Android phones which would use an API, authenticating by means of a token. The development of an API seems to be covered nicely here:
Sinatra - API - Authentication
What is not clear is now I might arrange the login procedure. Presumably it would be along these lines:
User selects what service to use, e.g. Twitter, FaceBook &c., by means of an in-app button on the Android device.
The Android app opens a webview to log in to the web app.
A token is somehow created, stored in the web app's database, and returned to the Android app so that it can be stored and used for subsequent API requests.
I'm not very clear on how point 3 might be managed - does anyone have any suggestions?
As no-one seems to have any suggestions, here's what I've come up with so far. I don't think it's very good, though.
I've added an API key to the user model, which is created when the user is first authenticated:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :uid, String
property :name, String
property :nickname, String
property :created_at, DateTime
property :api_key, String, :key => true
end
....
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]},
{ :uid => auth["uid"],
:nickname => auth["info"]["nickname"],
:name => auth["info"]["name"],
:api_key => SecureRandom.hex(20),
:created_at => Time.now })
session[:user_id] = user.id
session[:api_key] = user.api_key
flash[:info] = "Welcome, #{user.name}"
redirect "/success/#{user.id}/#{user.api_key}"
end
If the authorisation works then the api_key is supplied to the Android app, which will presumably store it on the device somewhere:
get '/success/:id/:api_key', :check => :valid_key? do
user = User.get(params[:id],params[:api_key])
if user.api_key == params[:api_key]
{'api_key' => user.api_key}.to_json
else
error 401
end
end
All API calls are protected as in the link in my original post:
register do
def check (name)
condition do
error 401 unless send(name) == true
end
end
end
helpers do
def valid_key?
user = User.first(:api_key => params[:api_key])
if !user.nil?
return true
end
return false
end
end
For public use I'll only allow SSL connections to the server. Any suggestions for improvement would be welcome.