Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ https://anusii.github.io/mypod/redirect.html
https://your-solid-server/
https://dev.empwr.au/mypod/
https://server/alice/
https://anushkavidanage.github.io/solid_auth/example_app/client-profile.jsonld
https://alice.solidcommunity.net/profile/card#me
https://solidcommunity.net/

# 20260605 gjw Failing solid servers

Expand Down
25 changes: 20 additions & 5 deletions example/.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.

version:
revision: "ff37bef603469fb030f2b72995ab929ccfc227f0"
revision: "c9a6c484230f8b5e408ec57be1ef71dee1e77020"
channel: "stable"

project_type: app
Expand All @@ -13,11 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: android
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: ios
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: linux
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: macos
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: web
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: windows
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020

# User provided section

Expand Down
6 changes: 6 additions & 0 deletions lib/src/auth/solid_auth_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import 'package:logging/logging.dart';
import 'package:oidc/oidc.dart';

import 'package:solid_auth/src/auth/solid_auth_session_store.dart';
import 'package:solid_auth/src/auth/solid_auth_store.dart';
import 'package:solid_auth/src/auth/solid_oidc_config.dart';
import 'package:solid_auth/src/auth/solid_oidc_manager_factory.dart';
import 'package:solid_auth/src/dpop/dpop_key_manager.dart';
Expand Down Expand Up @@ -170,6 +171,11 @@ class SolidAuthManager {
required String issuerUri,
List<String>? scopeOverride,
}) async {
// On web, remove any DPoP private key / session parameters a previous
// persistent build may have left in localStorage. No-op on native.

await purgeLegacyWebSecrets();
Comment on lines +174 to +177

await initForIssuer(
issuerUri,
scopeOverride: scopeOverride,
Expand Down
12 changes: 8 additions & 4 deletions lib/src/auth/solid_auth_session_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import 'dart:convert';

import 'package:logging/logging.dart';
import 'package:oidc_core/oidc_core.dart';
import 'package:oidc_default_store/oidc_default_store.dart';

import 'package:solid_auth/src/auth/solid_auth_store.dart';

final _log = Logger('solid_auth.SolidAuthSessionStore');

Expand Down Expand Up @@ -85,7 +86,10 @@ class SolidAuthSessionStore {
static const _privateKeyKey = 'solid_auth_rsa_private';
static const _publicKeyKey = 'solid_auth_rsa_public';

final _store = OidcDefaultStore();
// Platform-aware store: persistent (OS-backed) on native, in-memory on web
// so the DPoP private key is never written to localStorage (see
// [createSolidAuthStore]).
Comment on lines +89 to +91
final OidcStore _store = createSolidAuthStore();

/// Persists all parameters required to restore this session later.
///
Expand All @@ -96,7 +100,7 @@ class SolidAuthSessionStore {
required String privateKeyPem,
required String publicKeyPem,
}) async {
if (!_store.didInit) await _store.init();
await _store.init();
_log.fine('Saving session for issuer: $issuerUri');
await _store.setMany(
OidcStoreNamespace.secureTokens,
Expand Down Expand Up @@ -153,7 +157,7 @@ class SolidAuthSessionStore {
///
/// Should be called on logout or when the session is no longer valid.
Future<void> clearSession() async {
if (!_store.didInit) await _store.init();
await _store.init();
_log.fine('Clearing stored session');
await _store.removeMany(
OidcStoreNamespace.secureTokens,
Expand Down
100 changes: 100 additions & 0 deletions lib/src/auth/solid_auth_store.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/// OIDC storage selection for Solid-OIDC sessions.
///
/// Copyright (C) 2026, Software Innovation Institute, ANU.
///
/// Licensed under the MIT License (the "License").
///
/// License: https://choosealicense.com/licenses/mit/.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///
/// Authors: Tony Chen

library;

import 'package:logging/logging.dart';
import 'package:oidc/oidc.dart';
import 'package:oidc_default_store/oidc_default_store.dart';

final _log = Logger('solid_auth.SolidAuthStore');

// Pure-Dart web detection (this package does not depend on Flutter, so we
// cannot use `kIsWeb`). The `dart.library.js_interop` environment flag is set
// by the compiler on web targets (JS and WASM) and absent on native.

const bool _kIsWeb = bool.fromEnvironment('dart.library.js_interop');

// A single shared in-memory store used for the whole web session, so the OIDC
// manager and the session store operate on the same data within a session.

final OidcStore _webMemoryStore = OidcMemoryStore();

// The persistent session-store keys a previous (persistent) build wrote to web
// localStorage; used only to purge them from upgrading clients.

const Set<String> _legacyWebSessionKeys = {
'solid_auth_issuer_uri',
'solid_auth_scopes',
'solid_auth_rsa_private',
'solid_auth_rsa_public',
};

/// Returns the OIDC store used for DPoP keys, tokens and session state.
///
/// On the **web** platform this is a single shared in-memory store, so the DPoP
/// private key and the OIDC tokens are NEVER written to `localStorage`. On web,
/// `flutter_secure_storage` keeps its AES-GCM key unwrapped in the same
/// `localStorage` as the ciphertext, so persisting these secrets there would
/// let any same-origin script (XSS) or a storage snapshot recover both the
/// tokens and the DPoP private key — defeating DPoP entirely. The trade-off is
/// that a web session does not survive a page reload: the user re-authenticates
/// and a fresh DPoP key pair is generated.
///
/// Native platforms keep the persistent, OS-backed store (`OidcDefaultStore`),
/// so their sessions are restored across app restarts exactly as before.

OidcStore createSolidAuthStore() =>
_kIsWeb ? _webMemoryStore : OidcDefaultStore();

/// Remove any DPoP private key / session parameters a previous *persistent*
/// build left in web `localStorage`.
///
/// New sessions never write these on web (see [createSolidAuthStore]), but an
/// upgrading client may still have the old, exposed values on disk. This is a
/// best-effort, web-only cleanup; a no-op on native platforms. Short-lived OIDC
/// token entries are left to expire rather than enumerated here.

Future<void> purgeLegacyWebSecrets() async {
if (!_kIsWeb) {
return;
}
try {
final persistent = OidcDefaultStore();
await persistent.init();
await persistent.removeMany(
OidcStoreNamespace.secureTokens,
keys: _legacyWebSessionKeys,
);
Comment on lines +90 to +93
_log.fine('Purged legacy web session secrets from persistent storage');
} on Object catch (e) {
// Never fail login because of a best-effort cleanup.

_log.fine('purgeLegacyWebSecrets() skipped: ${e.runtimeType}');
}
}
6 changes: 3 additions & 3 deletions lib/src/auth/solid_oidc_manager_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ library;

import 'package:logging/logging.dart';
import 'package:oidc/oidc.dart';
import 'package:oidc_default_store/oidc_default_store.dart';

import 'package:solid_auth/src/auth/solid_auth_store.dart';
import 'package:solid_auth/src/auth/solid_oidc_config.dart';
Comment on lines +33 to 34
import 'package:solid_auth/src/dpop/dpop_key_manager.dart';
import 'package:solid_auth/src/dpop/dpop_token_generator.dart';
Expand Down Expand Up @@ -174,7 +174,7 @@ abstract class SolidOidcManagerFactory {
? OidcUserManager(
discoveryDocument: metadata.oidcMetadata,
clientCredentials: clientAuth,
store: OidcDefaultStore(),
store: createSolidAuthStore(),
settings: settings,
httpClient: config.httpClient,
keyStore: null,
Expand All @@ -185,7 +185,7 @@ abstract class SolidOidcManagerFactory {
Uri.parse(issuerUri),
),
clientCredentials: clientAuth,
store: OidcDefaultStore(),
store: createSolidAuthStore(),
settings: settings,
httpClient: config.httpClient,
keyStore: null,
Expand Down
Loading