Skip to content
Merged
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
22 changes: 2 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@ Opaquely authenticates [Solid](https://github.com/solid/) clients
## About

### What is this?

Solid currently supports two cross-origin authentication protocols,
[WebID-TLS](https://www.w3.org/2005/Incubator/webid/spec/tls/) and
[WebID-OIDC](https://github.com/solid/webid-oidc-spec).

This library abstracts away the implementation details of these specs so that
clients don't have to handle different authentication protocols.
This library facilitates authentication with Solid servers
by implementing [WebID-OIDC](https://github.com/solid/webid-oidc-spec).

### Why might I need this?

Expand Down Expand Up @@ -95,10 +90,6 @@ logout (storage?: Storage): Promise<void>

Clears the active user session.

WARNING: this is an unsupported use case in WebID-TLS. Once your browser
provides its client cert to a web server, there's no going back! So for
WebID-TLS, the only thing this will do is clear the session from the store.

### `fetch`

Fetches a resource from the web. Same API as
Expand All @@ -113,21 +104,12 @@ fetch: (url: RequestInfo, options?: Object) => Promise<Response>
### types

```
type webIdTlsSession = {
authType: WebIdTls,
idp: string,
webId: string
}

type webIdOidcSession = {
authType: WebIdOidc,
idp: string,
webId: string,
accessToken: string,
idToken: string
}

type session = webIdTlsSession | webIdOidcSession
```

## Logging in via the popup app
Expand Down
62 changes: 19 additions & 43 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { getSession, saveSession, clearSession } from './session'
import type { AsyncStorage } from './storage'
import { defaultStorage } from './storage'
import { currentUrlNoParams } from './url-util'
import * as WebIdTls from './webid-tls'
import * as WebIdOidc from './webid-oidc'

export type loginOptions = {
Expand All @@ -28,33 +27,11 @@ const defaultLoginOptions = (): loginOptions => {
export const fetch = (url: RequestInfo, options?: Object): Promise<Response> =>
authnFetch(defaultStorage())(url, options)

async function firstSession(
storage: AsyncStorage,
authFns: Array<() => Promise<?Session>>
): Promise<?Session> {
if (authFns.length === 0) {
return null
}
try {
const session = await authFns[0]()
if (session) {
return saveSession(storage)(session)
}
} catch (err) {
console.error(err)
}
return firstSession(storage, authFns.slice(1))
}

export async function login(
idp: string,
options: loginOptions
): Promise<?Session> {
options = { ...defaultLoginOptions(), ...options }
const webIdTlsSession = await WebIdTls.login(idp)
if (webIdTlsSession) {
return saveSession(options.storage)(webIdTlsSession)
}
const webIdOidcLogin = await WebIdOidc.login(idp, options)
return webIdOidcLogin
}
Expand All @@ -75,32 +52,31 @@ export async function popupLogin(options: loginOptions): Promise<?Session> {
export async function currentSession(
storage: AsyncStorage = defaultStorage()
): Promise<?Session> {
const session = await getSession(storage)
if (session) {
return session
let session = await getSession(storage)
if (!session) {
try {
session = await WebIdOidc.currentSession(storage)
} catch (err) {
console.error(err)
}
if (session) {
await saveSession(storage)(session)
}
}
return firstSession(storage, [WebIdOidc.currentSession.bind(null, storage)])
return session
}

export async function logout(
storage: AsyncStorage = defaultStorage()
): Promise<void> {
const session = await getSession(storage)
if (!session) {
return
}
switch (session.authType) {
case 'WebID-OIDC':
try {
await WebIdOidc.logout(storage)
} catch (err) {
console.warn('Error logging out:')
console.error(err)
}
break
case 'WebID-TLS':
default:
break
if (session) {
try {
await WebIdOidc.logout(storage)
} catch (err) {
console.warn('Error logging out:')
console.error(err)
}
await clearSession(storage)
}
return clearSession(storage)
}
80 changes: 1 addition & 79 deletions src/api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,28 +103,10 @@ describe('login', () => {
expect(await getStoredSession()).toBeNull()
})

describe('WebID-TLS', () => {
it('can log in with WebID-TLS', async () => {
expect.assertions(2)
const webId = 'https://localhost/profile#me'
nock('https://localhost/')
.head('/')
.reply(200, '', { user: webId })

const session = await login('https://localhost')
expect(session.webId).toBe(webId)
expect(await getStoredSession()).toEqual(session)
})
})

describe('WebID-OIDC', () => {
it('can log in with WebID-OIDC', async () => {
expect.assertions(6)
nock('https://localhost/')
// try to log in with WebID-TLS
.head('/')
.reply(200)
// no user header, so try to use WebID-OIDC
.get('/.well-known/openid-configuration')
.reply(200, oidcConfiguration)
.get('/jwks')
Expand All @@ -149,10 +131,6 @@ describe('login', () => {
it('uses the provided redirect uri', async () => {
expect.assertions(6)
nock('https://localhost')
// try to log in with WebID-TLS
.head('/')
.reply(200)
// no user header, so try to use WebID-OIDC
.get('/.well-known/openid-configuration')
.reply(200, oidcConfiguration)
.get('/jwks')
Expand All @@ -179,10 +157,6 @@ describe('login', () => {
it('strips the hash fragment from the current URL when providing the default redirect URL', async () => {
expect.assertions(6)
nock('https://localhost/')
// try to log in with WebID-TLS
.head('/')
.reply(200)
// no user header, so try to use WebID-OIDC
.get('/.well-known/openid-configuration')
.reply(200, oidcConfiguration)
.get('/jwks')
Expand Down Expand Up @@ -217,7 +191,6 @@ describe('currentSession', () => {
it('can find the current session if stored', async () => {
expect.assertions(2)
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand All @@ -244,10 +217,6 @@ describe('currentSession', () => {
// client by logging in, generating the IDP's response, and redirecting
// back to the app.
nock('https://localhost/')
// try to log in with WebID-TLS
.head('/')
.reply(200)
// no user header, so try to use WebID-OIDC
.get('/.well-known/openid-configuration')
.reply(200, oidcConfiguration)
.get('/jwks')
Expand Down Expand Up @@ -299,30 +268,13 @@ describe('currentSession', () => {
})

describe('logout', () => {
describe('WebID-TLS', () => {
it('just removes the current session from the store', async () => {
expect.assertions(1)
await saveSession(window.localStorage)({
authType: 'WebID-TLS',
idp: 'https://localhost',
webId: 'https://person.me/#me'
})
await logout()
expect(await getStoredSession()).toBeNull()
})
})

describe('WebID-OIDC', () => {
it('hits the end_session_endpoint and clears the current session from the store', async () => {
expect.assertions(7)
// To test currentSession with WebID-OIDC it's easist to set up the OIDC RP
// client by logging in, generating the IDP's response, and redirecting
// back to the app.
nock('https://localhost/')
// try to log in with WebID-TLS
.head('/')
.reply(200)
// no user header, so try to use WebID-OIDC
.get('/.well-known/openid-configuration')
.reply(200, oidcConfiguration)
.get('/jwks')
Expand Down Expand Up @@ -392,7 +344,6 @@ describe('fetch', () => {
it('handles 401s from WebID-OIDC resources by resending with credentials', async () => {
expect.assertions(1)
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand All @@ -413,7 +364,6 @@ describe('fetch', () => {

it('merges request headers with the authorization header', async () => {
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand All @@ -438,7 +388,6 @@ describe('fetch', () => {
it('does not resend with credentials if the www-authenticate header is missing', async () => {
expect.assertions(1)
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand All @@ -456,7 +405,6 @@ describe('fetch', () => {

it('does not resend with credentials if the www-authenticate header suggests an unknown scheme', async () => {
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand Down Expand Up @@ -508,7 +456,6 @@ describe('fetch', () => {
it('just sends one request when the RP is also the IDP', async () => {
expect.assertions(1)
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand All @@ -528,7 +475,6 @@ describe('fetch', () => {
it('just sends one request to domains it has already encountered', async () => {
expect.assertions(1)
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
Expand All @@ -538,7 +484,7 @@ describe('fetch', () => {

await saveHost(window.localStorage)({
url: 'third-party.com',
authType: 'WebID-OIDC'
requiresAuth: true
})

nock('https://third-party.com')
Expand All @@ -552,29 +498,5 @@ describe('fetch', () => {
const resp = await fetch('https://third-party.com/resource')
expect(resp.status).toBe(200)
})

it('does not send credentials to a familiar domain when that domain uses a different auth type', async () => {
expect.assertions(1)
await saveSession(window.localStorage)({
authType: 'WebID-OIDC',
idp: 'https://localhost',
webId: 'https://person.me/#me',
accessToken: 'fake_access_token',
idToken: 'abc.def.ghi',
sessionKey
})

await saveHost(window.localStorage)({
url: 'third-party.com',
authType: 'WebID-TLS'
})

nock('https://third-party.com')
.get('/resource')
.reply(401)

const resp = await fetch('https://third-party.com/resource')
expect(resp.status).toBe(401)
})
})
})
10 changes: 2 additions & 8 deletions src/authn-fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function shouldShareCredentials(
return false
}
const requestHost = await getHost(storage)(url)
return requestHost != null && session.authType === requestHost.authType
return requestHost != null && requestHost.requiresAuth
}
}

Expand All @@ -48,11 +48,5 @@ const fetchWithCredentials = async (
url: RequestInfo,
options?: Object
): Promise<Response> => {
switch (session.authType) {
case 'WebID-OIDC':
return WebIdOidc.fetchWithCredentials(session)(url, options)
case 'WebID-TLS':
default:
return fetch(url, options)
}
return WebIdOidc.fetchWithCredentials(session)(url, options)
}
Loading