Files
MyActivityPub/docs/OAUTH_LOGIN.md
2026-01-24 17:45:29 +01:00

365 líneas
9.8 KiB
Markdown

# OAuth Login and Authentication
This document explains the OAuth authentication implementation in My ActivityPub app.
## Overview
The app supports two modes:
1. **Guest Mode** (Default): Browse public federated timeline without authentication
2. **Authenticated Mode**: Login with OAuth to access your home timeline and instance
## Features
### Guest Mode
- Opens directly to the public federated timeline from `mastodon.social`
- No authentication required
- Browse posts from the entire fediverse
- Option to login at any time
### Authenticated Mode
When logged in, users can:
- Access their home timeline (posts from followed accounts)
- Switch between Public and Home timelines
- See their username in the app header
- Logout when desired
- Persistent login (saved across app restarts)
## OAuth Flow
### 1. App Registration
When a user initiates login:
1. User enters their instance domain (e.g., `mastodon.online`)
2. App registers itself with the instance via `/api/v1/apps`
3. Receives `client_id` and `client_secret`
4. Credentials are saved securely in DataStore
### 2. Authorization
1. App constructs authorization URL:
```
https://{instance}/oauth/authorize?
client_id={client_id}&
scope=read write follow&
redirect_uri=myactivitypub://oauth&
response_type=code
```
2. Opens URL in Custom Chrome Tab (in-app browser)
3. User authorizes the app on their instance
4. Instance redirects to: `myactivitypub://oauth?code={auth_code}`
5. App catches the deep link and extracts the code
### 3. Token Exchange
1. App exchanges authorization code for access token
2. POST to `/oauth/token` with:
- `client_id`
- `client_secret`
- `code` (from authorization)
- `grant_type=authorization_code`
3. Receives `access_token`
4. Token is saved securely in DataStore
### 4. Authenticated Requests
All subsequent API requests include:
```
Authorization: Bearer {access_token}
```
## Architecture
### Components
#### AuthRepository
**Location**: `data/repository/AuthRepository.kt`
Handles all authentication operations:
- `registerApp()`: Register OAuth app with instance
- `getAuthorizationUrl()`: Generate OAuth URL
- `obtainToken()`: Exchange code for token
- `verifyCredentials()`: Verify user account
- `logout()`: Clear stored credentials
- `userSession`: Flow of current session state
#### AuthViewModel
**Location**: `ui/viewmodel/AuthViewModel.kt`
Manages authentication state:
```kotlin
sealed class AuthState {
object Loading
object NotAuthenticated
object LoggingIn
data class NeedsAuthorization(val authUrl: String)
object LoginComplete
data class Authenticated(val session: UserSession)
data class Error(val message: String)
}
```
#### LoginScreen
**Location**: `ui/screens/LoginScreen.kt`
UI for login:
- Instance domain input
- Login button
- Continue as guest option
- Error display
### Data Models
#### UserSession
```kotlin
data class UserSession(
val instance: String, // e.g., "mastodon.social"
val accessToken: String, // OAuth access token
val account: Account? // User account info
)
```
#### AppRegistration
```kotlin
data class AppRegistration(
val id: String,
val name: String,
val clientId: String,
val clientSecret: String,
val redirectUri: String
)
```
#### TokenResponse
```kotlin
data class TokenResponse(
val accessToken: String,
val tokenType: String, // Usually "Bearer"
val scope: String, // "read write follow"
val createdAt: Long
)
```
## Security
### Token Storage
- Tokens stored using **DataStore Preferences**
- Encrypted at rest by Android system
- Not accessible to other apps
- Cleared on logout
### OAuth Best Practices
✅ **Implemented**:
- Authorization Code Flow (most secure)
- Custom Chrome Tabs (prevents phishing)
- HTTPS only
- State parameter handling
- Token refresh (TODO)
❌ **Not Implemented** (Future):
- PKCE (Proof Key for Code Exchange)
- Token refresh
- Token expiration handling
- Revocation on logout
## API Endpoints
### Register App
```
POST /api/v1/apps
```
**Request**:
```
client_name=My ActivityPub
redirect_uris=myactivitypub://oauth
scopes=read write follow
```
**Response**:
```json
{
"id": "123",
"name": "My ActivityPub",
"client_id": "...",
"client_secret": "...",
"redirect_uri": "myactivitypub://oauth"
}
```
### Obtain Token
```
POST /oauth/token
```
**Request**:
```
client_id={client_id}
client_secret={client_secret}
redirect_uri=myactivitypub://oauth
grant_type=authorization_code
code={authorization_code}
scope=read write follow
```
**Response**:
```json
{
"access_token": "...",
"token_type": "Bearer",
"scope": "read write follow",
"created_at": 1234567890
}
```
### Verify Credentials
```
GET /api/v1/accounts/verify_credentials
Authorization: Bearer {access_token}
```
**Response**:
```json
{
"id": "123",
"username": "alice",
"display_name": "Alice",
"avatar": "https://...",
...
}
```
## Configuration
### OAuth Scopes
Currently requested scopes:
- `read`: Read user data
- `write`: Post statuses, follow accounts
- `follow`: Follow/unfollow accounts
### Redirect URI
Fixed redirect URI:
```
myactivitypub://oauth
```
Registered in `AndroidManifest.xml`:
```xml
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myactivitypub"
android:host="oauth" />
</intent-filter>
```
## Usage Examples
### Login Flow (User Perspective)
1. **Open app**
- See public timeline (guest mode)
2. **Tap menu (⋮) → Login**
- Enter instance: `mastodon.social`
- Tap "Login" button
3. **Authorize in browser**
- Browser opens showing Mastodon login
- Enter credentials (if not logged in)
- Click "Authorize"
4. **Return to app**
- Automatically redirects back
- Shows "Loading..."
- Displays home timeline
5. **Use app**
- See home timeline (followed accounts)
- Tap menu → Switch to Public
- Tap menu → Logout (when done)
### Programmatic Usage
```kotlin
// In a Composable
val authViewModel: AuthViewModel = viewModel()
val userSession by authViewModel.userSession.collectAsState()
// Start login
authViewModel.startLogin("mastodon.social")
// Complete login (after OAuth callback)
authViewModel.completeLogin(authorizationCode)
// Logout
authViewModel.logout()
// Check if logged in
if (userSession != null) {
// User is authenticated
val username = userSession.account?.username
}
```
## Troubleshooting
### Common Issues
#### Login button doesn't work
**Symptoms**: Nothing happens when tapping Login
**Causes**:
- Invalid instance domain
- Network connectivity issues
- Instance doesn't support OAuth
**Solutions**:
1. Check internet connection
2. Verify instance domain (no `https://`, just domain)
3. Try a known instance like `mastodon.social`
#### Browser doesn't open
**Symptoms**: No browser appears after tapping Login
**Causes**:
- Chrome not installed
- Intent filter not configured
**Solutions**:
1. Install Chrome or another browser
2. Check `AndroidManifest.xml` has intent filter
#### Can't return to app after authorization
**Symptoms**: Stuck in browser after authorizing
**Causes**:
- Deep link not registered
- Activity launch mode incorrect
**Solutions**:
1. Verify `launchMode="singleTask"` in manifest
2. Check intent filter configuration
3. Manually return to app
#### Token errors
**Symptoms**: "Failed to obtain token" error
**Causes**:
- Invalid authorization code
- Expired code
- Network issues
**Solutions**:
1. Try logging in again
2. Check network connection
3. Clear app data and retry
### Debug Mode
To enable detailed logging:
```kotlin
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY // Change from BASIC
}
```
View logs:
```bash
adb logcat | grep -E "(OAuth|Auth|Token)"
```
## Future Enhancements
### Planned Features
- [ ] Token refresh mechanism
- [ ] PKCE support
- [ ] Multiple account support
- [ ] Account switching
- [ ] Remember last used instance
- [ ] Instance suggestions/autocomplete
- [ ] Token revocation on logout
- [ ] Offline mode with cached data
- [ ] Biometric authentication
- [ ] Session timeout
### Security Improvements
- [ ] Implement PKCE
- [ ] Add state parameter validation
- [ ] Token rotation
- [ ] Secure key storage (Android Keystore)
- [ ] Certificate pinning
## Testing
### Manual Testing
1. **Guest Mode**:
- Open app → Should show public timeline
- No login required
2. **Login Flow**:
- Tap menu → Login
- Enter `mastodon.social`
- Should open browser
- Authorize app
- Should return to app automatically
- Should show home timeline
3. **Timeline Switching**:
- Login first
- Tap menu → Switch to Public
- Should show public timeline
- Tap menu → Switch to Home
- Should show home timeline
4. **Logout**:
- While logged in
- Tap menu → Logout
- Should return to public timeline (guest mode)
### Automated Testing
```kotlin
@Test
fun authRepository_registerApp_success() = runTest {
val result = authRepository.registerApp("mastodon.social")
assertTrue(result.isSuccess)
}
@Test
fun authViewModel_login_updatesState() = runTest {
authViewModel.startLogin("mastodon.social")
// Verify state changes to LoggingIn, then NeedsAuthorization
}
```
## References
- [Mastodon OAuth Documentation](https://docs.joinmastodon.org/spec/oauth/)
- [RFC 6749 - OAuth 2.0](https://tools.ietf.org/html/rfc6749)
- [Android Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs/)
- [DataStore Documentation](https://developer.android.com/topic/libraries/architecture/datastore)
## Support
For issues related to authentication:
1. Check this documentation
2. Review the code in `AuthRepository.kt`
3. Check LogCat for errors
4. Open an issue on GitHub with:
- Instance you're trying to connect to
- Error messages
- Steps to reproduce