owned this note
owned this note
Published
Linked with GitHub
## Solid Tutorial
### Pre-requisites
* Node 8.10
* Appropriate entries in the `/etc/hosts` file
If you're going to be testing solid-server locally in multi-user mode, you will want to create entries for each user's subdomain in your `/etc/hosts` file (NOTE: unfortunately /etc/hosts does not support wildcard syntax like '127.0.0.1 *.localhost', which is why we need to add an entry for each user).
This tutorial assumes that you will be running your solid-server on `localhost`, in multi-user mode, and we will be registering a user with the account name `alice`.
This means that the user's WebID will be `https://alice.localhost:8443/profile/card#me`
In order for your OS to recognize the `alice.localhost` address, you will need to add the following line to your `/etc/hosts` file (don't forget to `sudo` when editing it):
```
127.0.0.1 alice.localhost
```
### Installation
1. Install the `solid-server` npm package:
```
git clone https://github.com/solid/node-solid-server.git
cd node-solid-server
npm install
```
2. Create a set of self-signed certificates ([instructions using `openssl`](https://github.com/solid/node-solid-server#how-do-i-get-an-ssl-key-and-certificate))
3. Initialize the server (create a config file):
```
npm run solid init
```
4. Start solid-server in test mode (disable SSL certificate checking, to work with self-signed certs above):
```
./bin/solid-test start -v
```
5. Register a user account for `alice`. Load `https://localhost:8443/` in your browser. Click Register, and use `alice` as the username. On the filesystem, this will create a user account folder (in `./data/` by default): `./data/alice.localhost/`
### Authentication
Before doing anything useful (like creating folders, reading and writing resources, etc), you need to authenticate to your solid server.
#### Force-user (for testing only)
Turn on force-authentication (temporarily bypass the auth system).
Edit the `config.json` file created during initialization, add:
```
"forceUser": "https://alice.localhost:8443/profile/card#me",
```
#### Auth using `oidc-web` client
First, install the [`oidc-web`](https://github.com/solid/oidc-web) library using `npm`:
```
npm install oidc-web
```
Lets create a simple Hello World type web app, starting with this `demo.html` file:
```htmlmixed=
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo App</title>
<!-- Bootstrap CSS and Theme for demo purposes -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<script src="./node_modules/oidc-web/dist/oidc-web.min.js"></script>
<style>
body {
margin-bottom: 3em;
}
</style>
</head>
<body>
<div class="container">
<div>
<h3>Demo Page</h3>
</div>
</div>
</body>
</html>
```
Couple things to note here:
- We're going to use [Bootstrap](https://getbootstrap.com/docs/4.0/getting-started/introduction/) for the UI (that's where the `<div class="container">` type stuff is coming from)
- We're loading the `oidc-web` client directly from `./node_modules`, since we `npm` installed it above. The OIDC web client is exported as a global (`window.OIDC`) once the lib is loaded by the browser.
Test it out (in alice's public folder):
And load up `http://alice.localhost:8443/public/demo.html` in a browser. Open up the `Console`, make sure there's no errors.
Now lets initialize an auth client instance -- we'll add a `<script>` section at the bottom, below the `</body>` close tag:
```htmlmixed=
...
</body>
<script>
// Lib is exported as window.OIDC
var OIDCWebClient = OIDC.OIDCWebClient
var options = { solid: true }
var auth = new OIDCWebClient(options)
// auth is now a client instance
</script>
</html>
```
Refresh the page, make sure there's no errors in the console.
Ok, now there's two things that are required to use OIDC authentication:
1. Calling `auth.login(issuerUrl)` -- once you know the URL of the issuer/identity provider. Figuring out which provider a user wants to log in with is the trickiest part of traditional decentralized authentication, and involves either a UI popup window, or a row of buttons (Facebook, Google, Twitter, etc etc) to help guide provider selection. (This is commonly known as the "Nascar problem".) For the purposes of this tutorial, we'll hardcode the issuer to `issuerUrl = 'https://localhost:8443'`
2. Calling `auth.currentSession()` as soon as the page is loaded (either the `DOMContentLoaded` page event in plain javascript, or whatever framework-specific "the app has loaded" event). This does two things -- it detects whether a user has already logged in (and has a current session saved in `localStore`), and also handles the post-login callback redirect.
Lets add the call to `auth.currentSession()` first, and see what an un-authenticated session looks like:
```htmlmixed=
...
</body>
<script>
// Lib is exported as window.OIDC
var OIDCWebClient = OIDC.OIDCWebClient
var options = { solid: true }
var auth = new OIDCWebClient(options)
// auth is now a client instance
// Using a standard "document loaded" event listener
// (equivalent to jQuery's $(document).ready())
// Trigger a currentSession() check on page load, in case user is logged in already
document.addEventListener('DOMContentLoaded', () => {
auth.currentSession()
.then(session => {
console.log(session)
})
})
</script>
</html>
```
In the console, you should see something like:
```
Object {
credentialType: "access_token",
issuer: undefined,
authorization: {},
sessionKey: undefined,
idClaims: undefined,
accessClaims: undefined
}
```
Now lets authenticate, (if the session is empty):
```htmlmixed=
...
</body>
<script>
var OIDCWebClient = OIDC.OIDCWebClient
var options = { solid: true }
var auth = new OIDCWebClient(options)
document.addEventListener('DOMContentLoaded', () => {
auth.currentSession()
.then(session => {
console.log('auth.currentSession():', session)
if (!session.hasCredentials()) {
console.log('Empty session, redirecting to login')
auth.login('https://localhost:8443')
} else {
console.log('hasCredentials() === true')
}
})
})
</script>
</html>
```
After login, you get redirected back to the original `demo.html` page, and now you should have a Session object with credentials.
More importantly, you also have a wrapped authenticated `fetch` function to work with (it's a getter function on the session object, which means it won't show up when you console.log the session):
Lets add a global `fetch` var, to contain that authenticated fetch function.
```htmlmixed=
...
</body>
<script>
var OIDCWebClient = OIDC.OIDCWebClient
var options = { solid: true }
var auth = new OIDCWebClient(options)
var fetch // set after currentSession()
document.addEventListener('DOMContentLoaded', () => {
auth.currentSession()
.then(session => {
console.log('auth.currentSession():', session)
fetch = session.fetch
if (!session.hasCredentials()) {
console.log('Empty session, redirecting to login')
auth.login('https://localhost:8443')
} else {
console.log('hasCredentials() === true')
}
})
})
</script>
</html>
```
Once you've logged in, test out this `fetch` function in the browser console.
For example, you can fetch alice's root `.acl` file:
```
fetch('https://alice.localhost:8443/.acl', {})
.then(response => response.text())
.then(contents => { console.log(contents) })
```
Note: best to try this in Firefox. Since you're most likely serving this `demo.html` as a non-https page, when you try to fetch an https resource (the root .acl) from a plain http page, Chrome will suppress that call and you'll get a `net::ERR_BLOCKED_BY_CLIENT` error.
### Reading/Writing resources
See [Solid REST API](https://github.com/solid/solid-spec/blob/master/api-rest.md)
#### Creating a folder (with `wget`)
If you're using force-authentication, you can immediately try creating a test folder on the command line using `wget`:
```bash
curl -vk https://alice.localhost:8443/ -H'Content-type: text/turtle' -H'Link: <http://www.w3.org/ns/ldp#BasicContainer>; rel="type"' -H'Slug: test' -XPOST
* Connected to alice.localhost (127.0.0.1) port 8443 (#0)
* WARNING: disabling hostname validation also disables SNI.
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: *.localhost
> POST / HTTP/1.1
> Host: alice.localhost:8443
> User-Agent: curl/7.54.0
> Accept: */*
> Content-type: text/turtle
> Link: <http://www.w3.org/ns/ldp#BasicContainer>; rel="type"
> Slug: test
>
< HTTP/1.1 201 Created
< X-Powered-By: solid-server
< Vary: Origin
< Access-Control-Allow-Credentials: true
< Access-Control-Expose-Headers: Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate
< Allow: OPTIONS, HEAD, GET, PATCH, POST, PUT, DELETE
< Link: <.acl>; rel="acl", <.meta>; rel="describedBy", <http://www.w3.org/ns/ldp#Container>; rel="type", <http://www.w3.org/ns/ldp#BasicContainer>; rel="type", <http://www.w3.org/ns/ldp#BasicContainer>; rel="type"
< Location: /test/
< Content-Type: text/plain; charset=utf-8
< Content-Length: 7
< ETag: W/"7-rM9AyJuqT6iOan/xHh+AW+7K/T8"
< set-cookie: connect.sid=s%3AcXpnL6QStdpPmaTnfRcXyz-VVY-f43b1.OXr6bxo0%2BIe6UQZIpYjw83UL8atmFaz%2F8mY%2ByjGZ6Ag; Domain=.ldnode.local; Path=/; Expires=Fri, 24 Nov 2017 17:09:03 GMT; HttpOnly; Secure
< Date: Thu, 23 Nov 2017 17:09:03 GMT
```
On a successful 201 response, note that the location of the newly created folder is returned in the `Location:` header:
```
Location: /test/
```
You can do the same thing with the authenticated fetch function (just pass in the slug and link headers), so:
```
fetch('https://alice.localhost:8443/public/', {
method: 'POST',
headers: {
'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
'Slug': 'test'
}
})
.then(response => console.log(response.headers))
```