Authentication Guide
This guide explains how to work with authentication in the OpenCloud Mobile app.
Authentication Flow
The app uses OpenID Connect (OIDC) for authentication with OpenCloud servers:
- Server Connection: User enters the server URL
- WebFinger Discovery: Discovers OIDC provider capabilities
- OIDC Configuration: Retrieves authentication endpoints
- Authorization: Redirects to browser for login
- Token Exchange: Exchanges authorization code for tokens
Implementation Details
Server Connection
When a user enters a server URL, several checks are performed:
- URL normalization (adding https:// if missing)
- Security check for HTTP URLs
- WebFinger discovery to verify OIDC support
// Example: Server URL handling
const { url: normalizedUrl, isInsecure } = AuthService.normalizeServerUrl(serverUrl);
// Handle insecure connection warning if needed
if (isInsecure) {
// Show warning and get user confirmation
}
// Initialize authentication
const { success } = await AuthService.initialize(normalizedUrl);
WebFinger and OIDC Discovery
The app uses WebFinger to discover the OpenID Connect provider:
// WebFingerService.discoverOidcIssuer
const webFingerResponse = await this.discover(serverUrl, resource);
const oidcLink = this.findLinkByRel(webFingerResponse, 'http://openid.net/specs/connect/1.0/issuer');
Then it fetches the OpenID configuration:
// OidcService.fetchConfiguration
const configUrl = `${baseUrl}/.well-known/openid-configuration`;
const response = await fetch(configUrl);
return await response.json();
Authentication Flow
The actual authentication happens through browser redirection:
// Get authorization URL
const authUrl = AuthService.getAuthorizationUrl();
// Open browser for authentication
await WebBrowser.openAuthSessionAsync(authUrl, authConfig.redirectUri);
After authentication, the app exchanges the authorization code for tokens:
// Exchange authorization code for tokens
const tokens = await AuthService.exchangeCodeForTokens(authCode);
Platform-Specific Configuration
The authentication system is configured for different platforms:
-
Client IDs:
- iOS:
OpenCloudIOS
- Android:
OpenCloudAndroid
- iOS:
-
Redirect URIs:
- iOS:
oc://ios.opencloud.eu
- Android:
oc://android.opencloud.eu
- iOS:
Implementing Custom Authentication
To customize the authentication flow:
-
Update Configuration:
// In config/app.config.ts
auth: {
clientId: 'your-client-id',
redirectUri: 'your-custom-scheme://callback',
defaultScopes: 'openid profile email',
} -
Register URL Scheme:
- Update iOS
Info.plist
and AndroidAndroidManifest.xml
as described in the Configuration Guide
- Update iOS
-
Handle Authentication Callback:
- Create a route handler for your redirect URI
- Extract the authorization code from the URL
- Exchange the code for tokens
-
Store and Use Tokens:
- Securely store the access and refresh tokens
- Include the access token in API requests
- Implement token refresh logic
Testing Authentication
Manual Testing
To test authentication during development:
- Use a real OpenCloud server with OIDC support
- Enter the server URL in the app
- Complete the authentication flow
- Check the logs for token information
Automated Testing
The project includes comprehensive unit tests for all authentication services with 100% code coverage:
-
WebFingerService Tests:
- Testing WebFinger discovery
- Testing link retrieval
- Testing OIDC issuer discovery
- Testing error handling
-
OidcService Tests:
- Testing configuration fetching
- Testing OIDC discovery
- Testing authorization URL generation
- Testing various parameter combinations
-
AuthService Tests:
- Testing URL normalization
- Testing service initialization
- Testing token exchange
- Testing secure and insecure connections
To run the authentication tests:
npx jest "services/__tests__"
To run tests with coverage report:
npx jest "services/__tests__" --coverage
To run specific service tests:
npx jest "services/__tests__/WebFingerService-test.ts"
npx jest "services/__tests__/OidcService-test.ts"
npx jest "services/__tests__/AuthService-test.ts"
When writing your own components that use these services, you can mock them in your tests:
// Example: Mocking AuthService in a component test
jest.mock('../services/AuthService');
// Then in your test
(AuthService.initialize as jest.Mock).mockResolvedValue({ success: true });