- Авторизация через Google в Android и проверка токена на сервере
- Небольшая подготовка
- Добавляем действие на кнопку
- Необходимые области доступа
- Регистрация нашего приложения.
- Код получения токена
- Проверяем токен на сервере. (PHP)
- Token-Based Authentication with Retrofit | Android OAuth 2.0
- Setup Project
- Setup models
- Setup Retrofit
- A follow-up on how to store tokens securely in Android
- Native Development Kit (NDK)
- Summary
- A small disclaimer
Авторизация через Google в Android и проверка токена на сервере
Недавно мне захотелось создать личный проект на андроиде, и основной вопрос был такой: как однозначно идентифицировать пользователя заставляя его делать как можно меньше телодвижений? Конечно же это аккаунт Google. Я пытался пробовать множество примеров в сети — однако API несколько раз обновилось за время своего существования, многие методы не работали, мои вопросы в Google+ по этому поводу либо были вообще никак не восприняты окружением, либо были вроде «Никогда такое не делал».
В этой статье я постараюсь как можно более просто для новичков (вроде меня) описать мой метод авторизации в Google на андроид, получения токена и проверке этого самого токена на сервере.
Небольшая подготовка
Для начала — у вас должны быть установлены Google Play Services в SDK. После их установки можно будет импортировать все необходимые библиотеки. Статья пишется с расчетом на Android Studio — он сам подсказывает, что необходимо импортировать.
У вас должно быть создано активити с кнопкой.
Чтобы было привычнее пользователю можете создать стандартную кнопку Google+ Sing-In
Выглядеть она будет вот так:
Просто добавьте в ваш Layout:
Добавляем действие на кнопку
Пишем в нашем активити:
Собственно присвоим кнопке действие — вызов интенда выбора аккаунта. Если вы работаете в Android Studio он сам вам подскажет, какие библиотеки нужно импортировать, так что это подробно тут я расписывать не буду.
startActivityForResult(intent, 123); — задает код с которым произойдет возврат. 123 это код возврата, он может быть каким угодно. Это необходимо, когда вы делаете несколько интендов, и вам надо обработать их по разному.
Необходимые области доступа
Обьявите эти переменные в классе. Это необходимые нам области доступа. Первый написано в google: «Позволяет определить аутентифицированного пользователя. Для этого при вызове API необходимо указать me вместо идентификатора пользователя Google+. » Второе разрешение нам необходимо для получения личных данных пользователя (Имя, Фамилия, адрес G+ страницы, аватар), и последнее для получения E-mail. Я посчитал это важным, ведь это вполне неизменный идентификатор для записи в бд.
Регистрация нашего приложения.
Изначально забыл этот пункт — исправляюсь.
Нам необходимо зайти на code.google.com/apis/console создать там проект, зайти в Credentials и создать новый Client ID для OAuth выбрав пункт Installed Application -> Android. Там нам необходимо ввести название нашего пакета и SHA1 сумму нашего ключа.
С этим у меня на самом деле было много проблем решил достаточно костыльным способом.
Нашел debug.keystore в %USERPROFILE%\.android\debug.keystore поместил в папку с проектом и прописал в build.grandle:
После чего нам нужно выполнить команду:
keytool -exportcert -alias androiddebugkey -keystore
/.android/debug.keystore -v -list
Сам keytool можно найти в SDK. Из вывода копируем SHA1 в нужное поле.
Как я понимаю метод временный, и для нормальной работы надо создать нормальный ключ. Но для тестирования этого достаточно.
Код получения токена
Где 123 — ваш код, который вы указали ранее, где AcrivityName — название вашего актитивити. Грубо говоря — мы скармливаем функции получения токена необходимые разрешения и имя аккаунта. И заметьте — это все происходит в фоновом режиме, после чего полученный токен передается в написанную мною функцию reg. Она уже отправляет токен и все необходимые данные на сервер.
Так как разрабатываю недавно, с исключениями пока что беда, если есть предложение — напишите в личку или в комментарии.
Проверяем токен на сервере. (PHP)
Хочу обратить внимание, полученный нами токен имеет тип Online. И действует он лишь 10 минут. Для получения offline токена (чтобы дольше работать с ним с сервера) обратитесь к этой инструкции developers.google.com/accounts/docs/CrossClientAuth
Собственно скармливаем токен в googleapis и забираем полученный JSON ответ.
Источник
Token-Based Authentication with Retrofit | Android OAuth 2.0
Feb 15, 2020 · 3 min read
Retrofit is a type-safe HTTP client by Square that was built for the Android platform. It offers an easy and clean way to make REST API network calls and parses the JSON/XML response(s) into Java Objects which we can then use in our app.
As a security measure, most API access points require us e rs to provide an authentication token that can be used to verify the identity of the user making the request so as to grant them access to data/ resources from the backend. The client app usually fetches the token upon successful login or registration then saves the token locally and appends it to subsequent requests so that the server can authenticate the user.
In this blog we are going to see a clean way to append the logged in user’s token to our app API requests once the user has logged in. Our use case assumes the user needs to fetch a list of posts from the server.
Setup Project
First we’ll proceed and create a new Android Studio project. For this project we’ll be using Kotlin however the same implementation works for Java.
Add the Retrofit dependencies to your app/build.gradle:
Then add the internet permission in your AndroidManifest.xml
Setup models
Let’s create the User.kt class that will contain the basic details of the User. For our use case it will only contain the user ID, first name, last name and email.
For login, the user will be required to provide the email and password so let’s create the LoginRequest.kt data class.
On successful login, the user will receive a response containing the status code, authentication token and user details. Let’s create the LoginResponse.kt.
Setup Retrofit
We will create a Constants.kt class that will hold our static variables.
Then we will create the ApiClient.kt class that will initialize our Retrofit client instance and the ApiService.kt interface where we will define our API request functions.
Fetching the token
In order to be able to save and fetch the token on the user’s device, we will create a SessionManager.kt class.
On successful login, we will save the fetched token.
Adding the token to our requests
Now that our user can login, we can finally fetch a list of posts. Let’s first create a sample Post.kt object.
And the corresponding PostsResponse.kt data class.
In order to fetch the list of posts, we can add the authorization token as a header to the function to fetch posts then pass it as a parameter:
This should work quite well and we should be able to fetch the list of posts. However using this method means for each and every authenticated request we will have to add the Header parameter and pass the token from the function making the request. Not clean, is it?
Using a request Interceptor
Fortunately, Retrofit uses Okhttp through which we can add interceptors to our retrofit client. Retrofit triggers the Interceptor instance whenever a request is made.
Let’s go ahead and make an AuthInterceptor.kt for our requests so that we can add the token to the request.
We will then update our ApiClient.kt to include the custom Okhttp client.
Then we can remove the header parameter from our request function and from the function making the request then just call the request functions directly. For the unauthenticated endpoints such as login, the token value from Session Manager will be null thus will not be added to the request.
Источник
A follow-up on how to store tokens securely in Android
As a prologue to this article, I want to remark a short sentence for the notional reader. This quote will be important as we move forward.
Absolute security does not exist. Security is a set of measures, being piled up and combined, trying to slow down the inevitable.
Almost three years ago, I wrote a post giving some ideas to protect String tokens from a hypothetical attacker decompiling our Android application. For the sake of remembrance, and in order to ward off the inescapable death of the Internet, I am reproducing some sections here.
One of the most common use cases happens when our application needs to communicate with a web service in order to exchange data. This data exchange can oscillate from a less to a more sensitive nature, and vary between a login request, user data alteration petition, etc.
The absolute first measure to be applied is using a SSL (Secure Sockets Layer) connection between the client and the server. Go again to the initial quote. This does not ensure an absolute privacy and security, although it makes a good initial job.
When you are using a SSL connection (like when you see the locker in your browser) it indicates that the connection between you and the server is encrypted. On a theoretical level, nothing can access the information contained within this requests (*)
(*) Did I mention that the absolute security does not exist? SSL connections can still be compromised. This article does not intend to provide an extensive list of all the possible attacks, but I want to let you know of a few possibilities. Fake SSL certificates can be used, as well as Man-in-the-Middle attacks.
Let’s move forward. We are assuming our client is communicating via an encrypted SSL channel with our backend. They are exchanging useful data, making their business, being happy. But we want to provide an additional security layer.
A next logical step used nowadays is to provide an authentication token or API Key to be used in the communication. It works this way. Our backend receives a petition. How do we know the petition comes from one of our verified clients, and not a random dude trying to gain access to our API? The backend will check if the client is providing a valid API Key. If the previous statement happens to be true, then we proceed with the request. Otherwise, we deny it and depending on the nature of our business we take some corrective measures (when this is happening, I particularly like to store the IP and IDs from the client to see how often this occurs. When the frequency is swelling more than it is desirable for my fine taste, I do consider a ban or observing closely what the impolite internet dude is trying to achieve).
Let’s construct our castle from the ground. In our app, we will likely add a variable called API_KEY that gets automatically injected in each request (if you are using Android, probably in your Retrofit client).
This is great, and works if we want to authenticate our client. The problem is that does not provide a very effective layer by itself.
If you use apktool to decompile the application and perform a search looking for strings, you will find in one of the resulting .smali files the following:
Yeah, sure. It does not say this is a validation Token, so we still need to go through a meticulous verification to decide how to reach this string and whether it can be used for authentication purposes or not. But you know where I am going: this is mostly a matter of time and resources.
Could Proguard help us to secure this String, so we do not have to worry about it? Not really. Proguard states in its FAQ that String encryption is not totally possible.
What about saving this String in one of the other mechanisms provided by Android, such as the SharedPreferences? This is barely a good idea. SharedPreferences can be easily accessed from the Emulator or any rooted device. Some years ago a guy called Srinivas proofed how the scored could be altered in a video-game. We are running out of options here!
Native Development Kit (NDK)
I am going to refresh here the initial model I proposed, and how we can as well iterate through it to provide a more secure alternative. Let’s image two functions that could serve to encrypt and decrypt our data:
Nothing fancy here. These two functions will take a key value and a string to be encoded or decoded. They will return the encrypted or the decrypted token, respectively. We would call the following function as follows:
Are you guessing the direction? That is right. We could encrypt and decrypt our token on demand. This provides an additional layer of security: when the code gets obfuscated, it is not anymore as straightforward as performing a String search and check the environment surrounding that String. But can you still figure out a problem that needs to be solved?
Give it a couple of seconds more if you have not figured it out yet.
Yes, you are right. We have an encryption key that is being also stored as String. This is adding more layers of security by obscurity, but we still have a token on plain text, regardless of whether this token is used for encryption or is the token per-se.
Let’s going to use now the NDK, and keep iterating our security mechanism.
NDK allows us to access a C++ code base from our Android code. As a first approach, let’s take a minute to think what to do. We could have a native C++ function that stores an API Key or whatever sensitive data we are trying to store. This function could later on be called from the code, and no string will be stored in any Java file. This would provide an automatic protection against decompiling techniques.
Our C++ function would look like follows:
It will be called easily in your Java code:
And the encryption/decryption function will be called as in the next snippet:
If we know generate an APK, obfuscate it, decompile it and try to access the string contained in the native function getSecretKey(), we will not be able to find it! Victory?
Not really. The NDK code can actually be disassembled and inspected. This is getting tougher, and you are starting to require more advanced tools and techniques. You got rid of 95% of the script kids, but a team with enough resources and motivation will still be able to access the token. Remember this sentence?
Absolute security does not exist. Security is a set of measures, being piled up and combined, trying to slow down the inevitable.
Hex Rays, for instance, makes a very good job at decompiling native files. I am sure there are a bunch of tools as well that could deconstruct any native code generated with Android (I am not associated with Hex Rays neither receiving any kind of monetary compensation from them).
So which solution could we use to communicate between a backend and a client without being flagged?
Generate the key in real time on the device.
Your device does not need to store any kind of key and deal with all the hassle of protecting a String literal! This is a very old technique used by services such as remote key validation.
- The client knows a function() that returns a key.
- The backend knows the function() implemented in the client
- The client generates a key through the function(), and this gets delivered to the server.
- The server validates it, and proceeds with the request.
Are you connecting the dots? Instead of having a native function that returns you a string (easily identifiable) why not having a function that returns you the sum of three random prime numbers between 1 and 100? Or a function that takes the current day expressed in unixtime and adds a 1 to each different digit? What about taking some contextual information from the device, such as the amount of memory being used, to provide a higher degree of entropy?
The last paragraph includes a series of ideas, but our hypothetical reader has hopefully taken the main point.
Summary
- Absolute security does not exist.
- Combining a set of protecting measures is the key to achieving a high degree of security.
- Do not store String literals in your code.
- Use the NDK to create a self-generated key.
Remember the first sentence?
Absolute security does not exist. Security is a set of measures, being piled up and combined, trying to slow down the inevitable.
I want to point once more that your goal is to protect as much as possible your code, without losing perspective that 100% of security is unattainable. But if you are able to protect your code in a way that requires a vast amount of resources to decrypt any sensible information you have, you will be able to sleep well and quiet.
A small disclaimer
I know. You got until here, thinking throughout the entire article “how is this guy not mentioning Dexguard, and going through all the hassle?”. You are right. Dexguard can actually obfuscate Strings, and they do a very good job at it. However, Dexguard pricing can be prohibitive. I have used Dexguard in previous companies with critical security systems, but this might not be an option for everybody. And, in Software Development as well as in life, the more options you have the richer and more abundant the world gets.
I write my thoughts about Software Engineering and life in general in my Twitter account. If you have liked this article or it did help you, feel free to share it, ♥ it and/or leave a comment. This is the currency that fuels amateur writers.
Источник