7.4 KiB
7.4 KiB
Farm Auth Service - Quick Start
Base URL
Development: http://localhost:3000
Production: https://your-domain.com
Authentication Flow
1. POST /auth/request-otp → User enters phone number
2. POST /auth/verify-otp → User enters OTP code → Get tokens
3. Use access_token in header → Authorization: Bearer <token>
4. POST /auth/refresh → Get new tokens when expired
5. POST /auth/logout → Revoke token
Endpoints
1. Request OTP
POST /auth/request-otp
Content-Type: application/json
{
"phone_number": "+919876543210"
}
Response:
{ "ok": true }
2. Verify OTP (Login)
POST /auth/verify-otp
Content-Type: application/json
{
"phone_number": "+919876543210",
"code": "123456",
"device_id": "android-device-id-123",
"device_info": {
"platform": "android",
"model": "Samsung Galaxy",
"os_version": "Android 14",
"app_version": "1.0.0"
}
}
Response:
{
"user": {
"id": "uuid",
"phone_number": "+919876543210",
"name": null,
"role": "user",
"user_type": null
},
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"needs_profile": true
}
Store: access_token and refresh_token securely (EncryptedSharedPreferences)
3. Refresh Token
POST /auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGc..."
}
Response:
{
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc..." ← Always save this new token!
}
Important: Refresh tokens rotate. Always save the new refresh_token.
4. Update Profile
PUT /users/me
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "John Doe",
"user_type": "seller" // or "buyer" or "service_provider"
}
Response:
{
"id": "uuid",
"phone_number": "+919876543210",
"name": "John Doe",
"role": "user",
"user_type": "seller"
}
5. Logout
POST /auth/logout
Content-Type: application/json
{
"refresh_token": "eyJhbGc..."
}
Response:
{ "ok": true }
Kotlin Data Classes
// Request Models
data class RequestOtpRequest(val phone_number: String)
data class VerifyOtpRequest(
val phone_number: String,
val code: String,
val device_id: String,
val device_info: DeviceInfo? = null
)
data class DeviceInfo(
val platform: String = "android",
val model: String? = null,
val os_version: String? = null,
val app_version: String? = null,
val language_code: String? = null,
val timezone: String? = null
)
data class RefreshRequest(val refresh_token: String)
data class UpdateProfileRequest(
val name: String,
val user_type: String // "seller" | "buyer" | "service_provider"
)
// Response Models
data class User(
val id: String,
val phone_number: String,
val name: String?,
val role: String,
val user_type: String?
)
data class VerifyOtpResponse(
val user: User,
val access_token: String,
val refresh_token: String,
val needs_profile: Boolean
)
data class RefreshResponse(
val access_token: String,
val refresh_token: String
)
Example: Kotlin HTTP Client
// Using Retrofit + OkHttp
interface AuthApi {
@POST("auth/request-otp")
suspend fun requestOtp(@Body request: RequestOtpRequest): Response<Unit>
@POST("auth/verify-otp")
suspend fun verifyOtp(@Body request: VerifyOtpRequest): Response<VerifyOtpResponse>
@POST("auth/refresh")
suspend fun refreshToken(@Body request: RefreshRequest): Response<RefreshResponse>
@PUT("users/me")
suspend fun updateProfile(
@Header("Authorization") token: String,
@Body request: UpdateProfileRequest
): Response<User>
@POST("auth/logout")
suspend fun logout(@Body request: RefreshRequest): Response<Unit>
}
// Usage
val api = Retrofit.Builder()
.baseUrl("http://your-api-url")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(AuthApi::class.java)
// Request OTP
api.requestOtp(RequestOtpRequest("+919876543210"))
// Verify OTP
val response = api.verifyOtp(
VerifyOtpRequest(
phone_number = "+919876543210",
code = "123456",
device_id = getDeviceId(),
device_info = DeviceInfo(platform = "android")
)
)
val tokens = response.body() // Save access_token & refresh_token
// Make authenticated request
val user = api.updateProfile(
"Bearer ${accessToken}",
UpdateProfileRequest("John", "seller")
)
Error Codes
| Code | Error | Solution |
|---|---|---|
| 400 | phone_number is required |
Include phone_number in request |
| 400 | Invalid or expired OTP |
Re-request OTP (expires in 10 min) |
| 401 | Invalid refresh token |
Force re-login |
| 401 | Missing Authorization header |
Include Authorization: Bearer <token> |
| 403 | Origin not allowed |
CORS issue (production) |
| 500 | Internal server error |
Retry later |
Token Storage (Android)
// Use EncryptedSharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
class TokenStorage(context: Context) {
private val prefs = EncryptedSharedPreferences.create(
context,
"auth_tokens",
MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveTokens(access: String, refresh: String) {
prefs.edit().apply {
putString("access_token", access)
putString("refresh_token", refresh)
apply()
}
}
fun getAccessToken() = prefs.getString("access_token", null)
fun getRefreshToken() = prefs.getString("refresh_token", null)
fun clear() = prefs.edit().clear().apply()
}
Token Expiration
- Access Token: 15 minutes (auto-refresh on 401)
- Refresh Token: 7 days
- OTP: 10 minutes
Phone Number Format
9876543210→ Auto-converted to+919876543210(10-digit = India)+919876543210→ Used as-is- Always send in E.164 format:
+<country_code><number>
Device ID
- Must be 4-128 alphanumeric characters
- Use: Android ID, Installation ID, or Firebase Installation ID
- Invalid formats are auto-hashed
Security Notes
- ✅ Store tokens in EncryptedSharedPreferences
- ✅ Auto-refresh access token on 401 errors
- ✅ Always save new refresh_token after refresh (tokens rotate)
- ✅ Logout clears tokens and revokes refresh token
- ⚠️ If refresh returns 401 → Force re-login (token compromised/reused)
Full Example Flow
// 1. Request OTP
api.requestOtp(RequestOtpRequest("+919876543210"))
// 2. Verify OTP & Save Tokens
val loginResponse = api.verifyOtp(...)
tokenStorage.saveTokens(loginResponse.access_token, loginResponse.refresh_token)
// 3. Use Access Token
val user = api.updateProfile("Bearer ${tokenStorage.getAccessToken()}", ...)
// 4. Handle Token Expiration (401) → Refresh
val refreshResponse = api.refreshToken(RefreshRequest(tokenStorage.getRefreshToken()!!))
tokenStorage.saveTokens(refreshResponse.access_token, refreshResponse.refresh_token)
// 5. Logout
api.logout(RefreshRequest(tokenStorage.getRefreshToken()!!))
tokenStorage.clear()