Skip to content
Open
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,9 @@ app.*.symbols
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock

#-----------------------------------------------------------------------
# Claude related files
#-----------------------------------------------------------------------
Claude.md
.claude/*
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ description of the update. Updates in the 0.1.n series are heading
toward a 0.2 release. The `[version timestamp user]` string is
utilised by the flutter version_widget package.

## 0.3

+ Implementing Authorization Code + PKCE, DPoP key binding (RFC 9449), and WebID-based issuer discovery using OpenID-certified [`package:oidc`](https://pub.dev/packages/oidc) [0.2.0 20260521 anushkavidanage]

## 0.2 Stability

+ Update Try Another WebID workflow [0.1.30 20260520 tonypioneer]
+ Update jose dependency [0.1.29 20260415 jesscmoore]
+ Review and cleanup for publication [0.1.28 20250925 gjw]
+ Remove jwt, update openid, use encrypt_plus [0.1.28 20250923 anushkavidanage]
+ Export openid_client [0.1.28 20250917 anushkavidanage]
Expand Down
253 changes: 131 additions & 122 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,157 +1,166 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
# solid_auth

For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
[![Flutter](https://img.shields.io/badge/Flutter-%2302569B.svg?style=for-the-badge&logo=Flutter&logoColor=white)](https://flutter.dev)
[![Dart](https://img.shields.io/badge/dart-%230175C2.svg?style=for-the-badge&logo=dart&logoColor=white)](https://dart.dev)

For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
[![GitHub License](https://img.shields.io/github/license/anusii/solid_auth)](https://raw.githubusercontent.com/anusii/solid_auth/dev/LICENSE)
[![GitHub Version](https://img.shields.io/badge/dynamic/yaml?url=https://raw.githubusercontent.com/anusii/solid_auth/master/pubspec.yaml&query=$.version&label=version&logo=github)](https://github.com/anusii/solid_auth/blob/dev/CHANGELOG.md)
[![Pub Version](https://img.shields.io/pub/v/solid_auth?label=pub.dev&labelColor=333940&logo=flutter)](https://pub.dev/packages/solid_auth)
[![GitHub Last Updated](https://img.shields.io/github/last-commit/anusii/solid_auth?label=last%20updated)](https://github.com/anusii/solid_auth/commits/dev/)
[![GitHub Commit Activity (main)](https://img.shields.io/github/commit-activity/w/anusii/solid_auth/dev)](https://github.com/anusii/solid_auth/commits/dev/)
[![GitHub Issues](https://img.shields.io/github/issues/anusii/solid_auth)](https://github.com/anusii/solid_auth/issues)

# Solid Auth
Solid-OIDC authentication for Flutter apps. Handles Authorization Code + PKCE, DPoP key binding (RFC 9449), and WebID-based issuer discovery. This package is built on the OpenID-certified [`package:oidc`](https://pub.dev/packages/oidc).

Solid Auth is an implementation of [Solid-OIDC
flow](https://solid.github.io/solid-oidc/) which can be used to
authenticate a client application to a Solid POD. Solid OIDC is built
on top of OpenID Connect 1.0.
---

The authentication process works with both Android and Web based
client applications. The package can also be used to create DPoP proof
tokens for accessing private data inside PODs after the
authentication.
## Features

This package includes the source code of two other packages,
[openid_client](https://pub.dev/packages/openid_client) and
[dart_jsonwebtoken](https://pub.dev/packages/dart_jsonwebtoken), with
slight modifications done to those package files in order to be
compatible with Solid-OIDC flow.
- **Solid-OIDC login** via Authorization Code + PKCE on all platforms (Android, iOS, Web, Windows, macOS, Linux)
- **DPoP key binding** - generates and manages RSA-2048 key pairs; injects DPoP proof headers automatically at every token request
- **WebID issuer discovery** - resolves an OIDC issuer from any WebID profile URL
- **Typed auth result** (`SolidAuthData`) - replaces the old raw `Map<String, dynamic>`

## Features
---

* Authenticate a client application to a Solid POD
* Create DPoP tokens for accessing data inside a POD
* Access public profile data of a POD using its WebID
## Installation

<!-- ## Getting started
```yaml
dependencies:
solid_auth: ^0.2.0
```

TODO: List prerequisites and provide or point to information on how to
start using the package. -->
<!-- If your app targets **web**, also declare the `fast_rsa` WASM worker assets in your app's `pubspec.yaml` (the package omits them from its own asset list):

## Usage
```yaml
flutter:
assets:
- packages/fast_rsa/web/assets/worker.js
- packages/fast_rsa/web/assets/wasm_exec.js
- packages/fast_rsa/web/assets/rsa.wasm
``` -->

To use this package add `solid_auth` as a dependency in your
`pubspec.yaml` file. An example project that uses `solid_auth` can be
found on
[github](https://github.com/anusii/solid_auth/tree/main/example).
---

### Authentication Example
## Quick Start

```dart
import 'package:solid_auth/solid_auth.dart';
import 'package:jwt_decoder/jwt_decoder.dart';

// Example WebID
String _myWebId = 'https://charlieb.solidcommunity.net/profile/card#me';
// 1. Create the manager once (e.g. at widget level or in a provider).
final auth = SolidAuthManager(
config: SolidOidcConfig(
clientId: 'https://your-domain/client-profile.jsonld',
redirectUri: Uri.parse('https://your-domain/redirect.html'),
postLogoutRedirectUri: Uri.parse('https://your-domain/redirect.html'),
scopes: SolidScopes.defaultScopes, // includes `webid` automatically
),
);

// 2. Login — resolves issuer from WebID, then runs Authorization Code + PKCE.
final authData = await auth.authenticate(
'https://pods.solidcommunity.au/alice-barnes/profile/card#me',
);
print(authData.webId); // https://pods.solidcommunity.au/alice-barnes/profile/card#me
print(authData.accessToken);

// 3. Generate a DPoP proof for a protected resource request.
final dpop = await DpopTokenGenerator.generateForRequest(
endpointUrl: 'https://pods.solidcommunity.au/alice-barnes/notepod/data/notes.ttl',
httpMethod: 'GET',
accessToken: authData.accessToken,
keyManager: auth.keyManager, // must be the same key bound to the access token
);
// Use in HTTP headers:
// 'Authorization': 'DPoP ${authData.accessToken}'
// 'DPoP': dpop

// 4. Logout.
await auth.logout();
```

// Get issuer URI
String _issuerUri = await getIssuer(_myWebId);

// Define scopes. Also possible scopes -> webid, email, api
final List<String> _scopes = <String>[
'openid',
'profile',
'offline_access',
];
`tryRestoreSession()` returns `null` if no session exists, if the refresh token has expired, or if any storage error occurs (in which case the stored session is cleared so the next login starts clean).

// Authentication process for the POD issuer
var authData = await authenticate(Uri.parse(_issuerUri), _scopes);
Calling `logout()` or `forgetUser()` always clears the stored session.

// Decode access token to recheck the WebID
String accessToken = authData['accessToken'];
Map<String, dynamic> decodedToken = JwtDecoder.decode(accessToken);
String webId = decodedToken['webid'];
---

```
## DPoP for Resource Requests

### Accessing Public Data Example
Every request to a Solid server protected resource needs both an `Authorization` header and a fresh DPoP proof. The proof must be signed by the **same** RSA key that was active during login (the access token's `cnf.jkt` claim is bound to it):

```dart
import 'package:solid_auth/solid_auth.dart';
Future<http.Response> getPrivateResource(
SolidAuthManager auth,
String resourceUrl,
) async {
final authData = auth.currentAuthData!;

final dpop = await DpopTokenGenerator.generateForRequest(
endpointUrl: resourceUrl,
httpMethod: 'GET',
accessToken: authData.accessToken,
keyManager: auth.keyManager,
);

return http.get(
Uri.parse(resourceUrl),
headers: {
'Authorization': 'DPoP ${authData.accessToken}',
'DPoP': dpop,
},
);
}
```

// Example WebID
String _myWebId = 'https://charlieb.solidcommunity.net/profile/card#me';
> **Important:** Always pass `keyManager: auth.keyManager`. The default (`DpopKeyManager.getInstance()`) returns the current singleton, which may be a freshly-generated key after an app restart, producing a thumbprint that the server will reject.

// Get issuer URI
Future<String> profilePage = await fetchProfileData(_myWebId);
---

```
## Platform Setup

### Generating DPoP Token Example
`redirectUri` and `postLogoutRedirectUri` must be registered in your [client ID document](https://solid.github.io/solid-oidc/#clientids-document) (`client-profile.jsonld`) and match the correct format for each platform:

```dart
import 'package:solid_auth/solid_auth.dart';
| Platform | URI format | Notes |
|---|---|---|
| Web | `https://your-domain/redirect.html` | Must be same origin as the app - `oidc` uses `BroadcastChannel` (same-origin only) |
| Android / iOS | `com.example.app://redirect` | Custom URI scheme registered with the OS |
| Windows / Linux / macOS | `http://localhost:4400/redirect` | **Fixed port required** - see below |

String endPointUrl; // The URL of the resource that is being requested
KeyPair rsaKeyPair; // Public/private key pair (RSA)
dynamic publicKeyJwk; // JSON web key of the public key
String httpMethod; // Http method to be used (eg: POST, PATCH)
### Desktop: use a fixed port

// Generate DPoP token
String dPopToken = genDpopToken(endPointUrl, rsaKeyPair, publicKeyJwk, httpMethod);
`oidc_desktop` binds a loopback HTTP server to the port in your `redirectUri`. If you use port `0`, the OS assigns a random port that is never registered in the client document, causing the Solid server to reject logout with `post_logout_redirect_uri not registered`. Use a fixed port (e.g. `4400`) in both the app and the client document.

```
Both `redirect_uris` and `post_logout_redirect_uris` in the client ID document must list every URI used across platforms:

## Additional information

The source code can be accessed via [GitHub
repository](https://github.com/anusii/solid_auth). You can also file
issues you face at [GitHub
Issues](https://github.com/anusii/solid_auth/issues).

### Running Solid Auth in web applications

In order to successfully run `solid auth` in a web application you
also need to create a custom `callback.html` file inside the `web`
directory. After created simply copy and paste the following code into
that file.

```html
<!DOCTYPE html>
<html>

<head>
<script>
const AUTH_DESTINATION_KEY = "openidconnect_auth_destination_url";
const AUTH_RESPONSE_KEY = "openidconnect_auth_response_info";

window.onload = function () {
if (window.opener && window.opener !== window) {
// Used when working as a popup.
// Uses post message to respond to the parent window.
var parent = window.opener ?? window.parent;
parent.postMessage(location.href, "*");
} else { //Used for redirect loop functionality.
//Get the original page destination
const destination = sessionStorage.getItem(AUTH_DESTINATION_KEY || "/");
sessionStorage.removeItem(AUTH_DESTINATION_KEY);

// Store the current window location that will be used
// to get the information for authentication
sessionStorage.setItem(AUTH_RESPONSE_KEY, window.location);

// Redirect to where we're going so that we can
// restore state completely
location.assign(destination);
}
}
</script>
</head>

<body>
</body>

</html>
```json
{
"redirect_uris": [
"https://your-domain/redirect.html",
"http://localhost:4400/redirect"
],
"post_logout_redirect_uris": [
"https://your-domain/redirect.html",
"http://localhost:4400/redirect"
]
}
```

For Android, iOS, and other platform-specific setup steps (manifest entries, URL schemes, etc.), follow the [`package:oidc` Getting Started guide](https://bdaya-dev.github.io/oidc/oidc-getting-started/).

---

## Migration Guide - 0.1.x → 0.2.x

> [!IMPORTANT]
> Upgrading from `0.1.x` to `0.2.x` is a **breaking change**. The underlying architecture has been re-engineered. The forked `openid_client` is replaced by the OpenID-certified `package:oidc`, and the top-level `authenticate()` function is replaced by `SolidAuthManager`. Please review the full README and use the table below to update your call sites.

| Old (0.1.x) | New (0.2.x) |
|---|---|
| `String issuer = await getIssuer(webId)` | `WebIdUtils.getIssuer(webId)` (same signature) |
| `var data = await authenticate(issuerUri, scopes)` | `await SolidAuthManager.authenticate(webIdOrIssuer)` - returns `SolidAuthData` |
| `data['accessToken']` | `authData.accessToken` |
| `data['idToken']` | `authData.idToken` |
| `genDpopToken(url, keyPair, jwk, method)` | `DpopTokenGenerator.generateForRequest(endpointUrl:, httpMethod:, accessToken:, keyManager: auth.keyManager)` |
| `fetchProfileData(webId)` | Removed - use `http` + parse the Turtle response directly |
Loading
Loading