Belajar One Time Password (OTP) di Android (bagian 1)
Membuat server OTP dengan Spring Boot dan Heroku
Series
Pengenalan
One Time Password atau sering disingkat dengan OTP merupakan salah satu metode keamanan pada aplikasi dan umumnya dipakai sebagai keamanan tambahan pada pengaturan Login dimana, kita sering melihatnya pada aplikasi-aplikasi umum sebagai Two Factor Authentication. Berikut gambaran beberapa aplikasi yang menggunakan metode ini.
Series
Adapun pada artikel ini kita akan bagi menjadi 2 bagian yaitu sebagai berikut.
- Bagian pertama, kita akan membuat server yang berfungsi untuk mengirimkan kode OTP.
- Bagian kedua, kita akan membuat aplikasi Android yang berfungsi untuk membaca kode OTP-nya lewat SMS.
Persiapan
Adapun beberapa hal yang kita pakai pada artikel bagian pertama ini ialah sebagai berikut.
- Spring Boot, untuk membuat server-nya kita menggunakan framework Spring Boot. Alasan mengapa kita pakai Spring Boot ialah karena saya sendiri bisa Java dan Kotlin jadi, saya rasa sudah sewajarnya jika saya memilih Spring Boot. Awalnya sih mau pakai NodeJS namun, karena saya tidak ada basic di JS jadi, akan lebih sulit untuk mempelajarinya.
- Heroku, untuk menjalankan server yang sudah kita buat tadi maka, kita perlu menjalankannya pada sebuah cloud platform. Dan pilihan saya jatuh kepada Heroku karena gratis dan juga mudah melakukan pengaturannya.
- PostgreSQL, untuk database-nya saya contohkan pakai PostgreSQL saja. Alasannya mengapa saya pilih PostgreSQL ialah karena di Heroku yang gratis pakai PostgreSQL jadi, saya tidak ada pilihan lain ya 😉.
- Text Local, untuk SMS gateway-nya kita pakai yang sudah siap pakai dan gratis juga tentunya namun, gratis-nya terbatas ya. Untuk SMS gateway-nya saya pakai https://www.textlocal.com/integrations/api/.
Registrasi SMS Gateway
Untuk mengirim SMS-nya kita memerlukan SMS gateway yang siap pakai saja ataupun jika kita bisa buat sendiri SMS gateway-nya juga tidak ada masalah sih. Untuk SMS gateway-nya silakan kita registrasi di situsnya langsung https://www.textlocal.com/integrations/api/. Untuk proses registrasinya tidak akan saya jelaskan ya. Lalu, jika sudah selesai registrasi coba ambil nilai api key-nya.
Memulai Pembuatan Projek Server
Pertama, silakan buat projek Spring Boot. Pada artikel ini saya pakai IntelliJ IDEA untuk membuat projeknya. Berikut langkah-langkahnya.
Setelah selesai buat projeknya coba buka file pom.xml dan pastikan isinya kurang lebih seperti berikut.
Catatan: saya ada menambahkan dependency OkHttp
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ysn</groupId>
<artifactId>server_otp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>server_otp</name>
<description>Demo project for Spring Boot as Server OTP</description>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.2.71</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
Untuk mengetes apakah projek kita sudah benar atau belum coba kita buat endpoint sederhana. Buka file class utamanya atau yang ada method main-nya. Kemudian isi dengan kode berikut.
@RestController
@SpringBootApplication
class ServerOtpApplication {
@GetMapping("/api/index")
fun index(): ResponseEntity<Map<String, Any>> {
val responseData = HashMap<String, Any>()
responseData["time_server"] = System.currentTimeMillis()
return ResponseEntity(responseData, HttpStatus.OK)
}
}
fun main(args: Array<String>) {
runApplication<ServerOtpApplication>(*args)
}
Lalu coba jalankan kode tersebut dari IDE atau dari terminal tinggal ketik perintah berikut.
mvn spring-boot:run
Catatan: pastikan ketika kita menjalankan perintah mvn spring-boot:run harus sudah berada didalam direktori projeknya
Jika sudah running, coba kita buka browser dan ketik localhost:8080/api/index maka, akan keluar respon dari kode yang sudah kita buat tadi.
Jika respon-nya sudah seperti pada gambar diatas maka, projek kita sudah benar pembuatannya.
Pembuatan Database
Untuk database pada artikel ini kita cuma perlu buat satu tabel saja dimana, tabel tersebut dari field-field berikut.
- id
- code
- is_active
Sekarang coba kita buat database-nya terlebih dahulu di PostgreSQL dengan cara buka terminal lalu ketik perintah berikut untuk masuk ke terminal PostgreSQL-nya.
psql
Setelah itu kita buat database-nya dengan cara ketik perintah berikut.
create database server_otp;
Selanjutnya coba kita lihat apakah database-nya sudah berhasil dibuat atau belum dengan perintah berikut.
\l
Selanjutnya coba kita buat table-nya lewat Java Persistence Layer (JPA). Buat file class baru bernama Otp dan isi dengan kode berikut.
@Entity
@Table(name = "Otp")
class Otp (
@GenericGenerator(
name = "otpSequenceGenerator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = [
Parameter(name = "sequence_name", value = "otpSequence"),
Parameter(name = "initial_value", value = "1"),
Parameter(name = "increment_value", value = "1")
]
)
@GeneratedValue(generator = "otpSequenceGenerator")
@Id
var id: Long = 0L,
var code: String = "",
var isActive: Boolean = false
)
Lalu, kita config pengaturan koneksi database-nya dengan cara buka file application.properties dan isi dengan kode berikut.
# Database properties
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/server_otp
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
Sekarang coba kita jalankan lagi projek Spring Boot-nya dan kemudian cek apakah sudah terbuat table otp di database-nya.
Repository dan Service
Repository ini berfungsi sebagai lapisan untuk melakukan semua operasi database secara langsung. Sekarang coba kita buat interface OtpRepository dan isi dengan kode berikut.
@Repository
interface OtpRepository : CrudRepository<Otp, Long> {
fun findByCode(code: String): Otp?
}
Service ini berfungsi sebagai lapisan yang menghubungkan lapisan database ke lapisan controller. Berikut gambaran relasinya.
Sekarang kita buat interface service OtpService seperti berikut.
interface OtpService {
fun createOtp(): String
fun updateOtp(code: String): Boolean
fun deleteOtp(code: String): Boolean
}
Lalu, kita buat class implement-nya dari interface OtpService seperti berikut.
@Service(value = "otpService")
class OtpServiceImpl : OtpService {
@Autowired
private lateinit var otpRepository: OtpRepository
override fun createOtp(): String {
var newCodeOtp: String?
while (true) {
newCodeOtp = ""
for (a in 1..4) {
newCodeOtp += Random().nextInt(10)
}
otpRepository.findByCode(newCodeOtp) ?: break
}
val newOtp = Otp(code = newCodeOtp!!, isActive = true)
otpRepository.save(newOtp)
return newCodeOtp
}
override fun updateOtp(code: String): Boolean {
val otpLocal = otpRepository.findByCode(code)
return if (otpLocal != null && otpLocal.isActive) {
otpLocal.isActive = false
otpRepository.save(otpLocal)
true
} else {
false
}
}
override fun deleteOtp(code: String): Boolean {
val otpLocal = otpRepository.findByCode(code)
otpLocal?.isActive = true
return if (otpLocal != null) {
otpRepository.delete(otpLocal)
true
} else {
false
}
}
}
Controller
Buat file class baru bernama OtpController dan isi dengan kode berikut.
@RestController
@RequestMapping("/api/otp")
class OtpController {
@Autowired
private lateinit var otpService: OtpService
@PostMapping("/send")
fun sendOtp(@RequestParam phoneNumber: String): ResponseEntity<Map<String, Any>> {
val codeOtp = otpService.createOtp()
val formRequestBody = FormBody.Builder()
.add("apikey", "f8NjAKbSJNQ-y5aF90RSBBexj4DoxkbKLYPDoHqzho")
.add("message", codeOtp)
.add("sender", "System")
.add("numbers", phoneNumber)
.build()
val request = Request.Builder()
.url("https://api.txtlocal.com/send/")
.post(formRequestBody)
.build()
val call = OkHttpClient().newCall(request)
val response = call.execute()
val responseData = HashMap<String, Any>()
responseData["time_server"] = System.currentTimeMillis()
val isSuccessful = response.isSuccessful
val responseCode = response.code()
val jsonObjectResponseFromServer = JSONObject(response.body().string())
val responseStatusMessage = jsonObjectResponseFromServer.getString("status")
System.out.println("responseDataFromServer: $jsonObjectResponseFromServer")
if (isSuccessful && responseCode == 200 && responseStatusMessage == "success") {
responseData["success"] = true
} else {
responseData["success"] = false
otpService.deleteOtp(codeOtp)
}
return ResponseEntity(responseData, HttpStatus.OK)
}
@PostMapping("/update")
fun updateOtp(@RequestParam codeOtp: String): ResponseEntity<Map<String, Any>> {
val responseData = HashMap<String, Any>()
responseData["time_server"] = System.currentTimeMillis()
responseData["success"] = otpService.updateOtp(codeOtp)
return ResponseEntity(responseData, HttpStatus.OK)
}
}
Bisa kita lihat pada kode diatas kita ada membuat dua endpoint dengan url seperti berikut.
- localhost:8080/api/otp/send?phoneNumber=:value berfungsi sebagai pengirim kode OTP-nya dari server kita ke server SMS gateway-nya dengan cara awalnya kita buat terlebih dahulu kode OTP-nya di server kita lalu kita panggil SMS gateway-nya dari server kita dan jika kita dapat respon berhasil dari server SMS gateway-nya maka, kita kembalikan respon berhasil ke client kita namun, jika respon dari SMS gateway-nya gagal maka, kita hapus terlebih dahulu kode OTP-nya yang sebelumnya sudah tersimpan di database kita lalu, kita kirimkan respon gagal ke client.
- localhost:8080/api/otp/update?codeOtp=:value berfungsi untuk mengubah status kode OTP-nya dari yang aktif (true) menjadi tidak aktif (false) agar kode tersebut tidak bisa dipakai lagi.
Testing Lokal
Sekarang coba kita jalankan lagi projeknya dan testing lokal pakai Postman.
- Coba panggil endpoint pertama dengan method POST dengan url
localhost:8080/api/otp/send?phoneNumber=nomor_hp
dimana, nomor_hp-nya itu kita pakai kode negara Indonesia yaitu tambahkan +62 didepannya. - Jika sudah berhasil maka, langkah selanjutnya adalah coba kita update status kode OTP tersebut menjadi tidak aktif.
Upload projek ke Heroku
Setelah berhasil kita testing di lokal maka, saatnya kita coba upload projek server kita ini ke Heroku. Pada artikel ini tidak akan saya jelaskan bagaimana cara registrasi di Heroku dan hanya saya jelaskan dari awal pembuatan projek di Heroku sampai deploy projek server kita di Heroku. Berikut langkah-langkahnya
- Buka url https://www.heroku.com/
- Lalu login.
- Kemudian, buat projek baru di Heroku.
- Lalu, isi keterangan projek seperti berikut.
- Selanjutnya, pilih Create app.
- Kemudian, langkah berikutnya ikuti keterangan seperti pada gambar berikut.
Sebenarnya langkah pada gambar terakhir maksudnya adalah kita harus mengatur git di projek kita agar bisa di-push ke repository kita di Heroku. Jika susah mengikuti langkah-langkah pada gambar terakhir maka, silakan ikut perintah-perintah terminal berikut.
Jika ada gagal dalam proses mengatur heroku di projek lokal maka, pastikan terlebih dahulu bahwa kita telah login ke heroku di terminal-nya dengan perintah
heroku login
Jika sudah kita push projek lokal kita ke Heroku maka, tampilannya di terminal kurang lebih seperti berikut dimana, ada keterangan bahwa server kita sudah di-deploy.
Testing Heroku
Kemudian, coba kita test lagi dengan cara yang sama di testing lokal namun, kita ganti base url-nya dari localhost:8080
menjadi https://server-otp.herokuapp.com
Kesimpulan
Di artikel bagian pertama ini kita sudah bisa membuat Server OTP-nya. Jadi, di artikel bagian keduanya kita akan bahas bagaimana cara membaca kode OTP-nya lewat SMS di Android. Untuk projek server pada artikel ini bisa dilihat di Github.