501 lines
16 KiB
Markdown
501 lines
16 KiB
Markdown
oauth3.js
|
|
=========
|
|
|
|
| *oauth3.js*
|
|
| [issuer.html](https://git.oauth3.org/OAuth3/issuer.html)
|
|
| [issuer.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js)
|
|
| [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv)
|
|
| Sponsored by [ppl](https://ppl.family)
|
|
|
|
The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation
|
|
(Yes! works in browsers and node.js with no extra dependencies or bloat and no hacks!)
|
|
|
|
Instead of bloating your webapp and ruining the mobile experience,
|
|
you can use a single, small javascript file for all OAuth3 providers
|
|
(and almost all OAuth2 providers) with a seamless experience.
|
|
|
|
Also, instead of complicated (or worse - insecure) CLI and Desktop login methods,
|
|
you can easily integrate an OAuth3 flow (or broker) into any node.js app (i.e. Electron, Node-Webkit)
|
|
with 0 pain.
|
|
|
|
If you have no idea what you're doing
|
|
------------
|
|
|
|
(people who know what they're doing should skip ahead to the tl;dr instructions)
|
|
|
|
1. Create a folder for your project named after your app, such as `example.com/`
|
|
2. Inside of the folder `example.com/` a folder called `assets/`
|
|
3. Inside of the folder `example.com/assets` a folder called `oauth3.org/`
|
|
4. Download [oauth3.js-v1.zip](https://git.oauth3.org/OAuth3/oauth3.js/repository/archive.zip?ref=v1)
|
|
5. Double-click to unzip the folder.
|
|
6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/`
|
|
7. Copy the folder `_apis` into the folder `example.com/`
|
|
9. Add `<script src="assets/oauth3.org/oauth3.core.js"></script>` to your `index.html`
|
|
9. Add `<script src="app.js"></script>` to your `index.html`
|
|
10. Create files in `example.com` called `app.js` and `index.html` and put this in it:
|
|
|
|
`index.html`:
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
</head>
|
|
<body>
|
|
|
|
<input type="url" placeholder="ex: https://oauth3.org" class="js-provider-uri">
|
|
<button type="button" class="js-login">Login</button>
|
|
<button type="button" class="js-logout">Logout</button>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.1.1.js"
|
|
integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA="
|
|
crossorigin="anonymous"></script>
|
|
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
|
<script src="app.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
`app.js`:
|
|
```js
|
|
var OAUTH3 = window.OAUTH3;
|
|
var auth = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id)
|
|
|
|
|
|
// this is any OAuth3-compatible provider, such as oauth3.org
|
|
// in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc
|
|
//
|
|
function onChangeProvider(_providerUri) {
|
|
// example https://oauth3.org
|
|
return oauth3.setIdentityProvider(providerUri);
|
|
}
|
|
|
|
|
|
// This opens up the login window for the specified provider
|
|
//
|
|
function onClickLogin() {
|
|
|
|
return oauth3.authenticate().then(function (session) {
|
|
|
|
console.info('Authentication was Successful:');
|
|
console.log(session);
|
|
|
|
// You can use the PPID (or preferably a hash of it) as the login for your app
|
|
// (it securely functions as both username and password which is known only by your app)
|
|
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
|
//
|
|
console.info('Secure PPID (aka subject):', session.token.sub);
|
|
|
|
return oauth3.request({
|
|
url: 'https://oauth3.org/api/issuer@oauth3.org/inspect'
|
|
, session: session
|
|
}).then(function (resp) {
|
|
|
|
console.info("Inspect Token:");
|
|
console.log(resp.data);
|
|
|
|
});
|
|
|
|
}, function (err) {
|
|
console.error('Authentication Failed:');
|
|
console.log(err);
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// This opens up the logout window
|
|
//
|
|
function onClickLogout() {
|
|
|
|
return oauth3.logout().then(function () {
|
|
localStorage.clear();
|
|
|
|
console.info('Logout was Successful');
|
|
|
|
}, function (err) {
|
|
console.error('Logout Failed:');
|
|
console.log(err);
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// initialize the provider to be oauth3.org (or any compatible provider)
|
|
//
|
|
onChangeProvider('oauth3.org');
|
|
|
|
|
|
$('body').on('click', '.js-login', onClickLogin);
|
|
$('body').on('click', '.js-logout', onClickLogout);
|
|
$('body').on('change', 'input.js-provider-uri', onChangeProvider);
|
|
```
|
|
|
|
Copy the `example.com/` folder to your webserver.
|
|
|
|
|
|
Example
|
|
-------
|
|
|
|
If you had a simple website / webapp for `example.com` with only the most necessary files,
|
|
it might look like this:
|
|
|
|
```
|
|
example.com
|
|
│
|
|
│
|
|
├── _apis
|
|
│ └── oauth3.org
|
|
│ ├── callback.html
|
|
│ ├── directives.json
|
|
│ └── index.html
|
|
├── assets
|
|
│ └── oauth3.org
|
|
│ └── oauth3.core.js
|
|
│
|
|
│
|
|
├── css
|
|
│ └── main.css
|
|
├── index.html
|
|
└── js
|
|
└── app.js
|
|
```
|
|
|
|
Installation (if you know what you're doing)
|
|
------------
|
|
|
|
**Advanced Installation with `git`**
|
|
|
|
```bash
|
|
# Navigate to your web site or web app
|
|
pushd /path/to/your/web/app
|
|
|
|
|
|
# clone the project as assets/oauth3.org
|
|
mkdir -p assets
|
|
git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org
|
|
pushd assets/oauth3.org
|
|
git checkout v1
|
|
popd
|
|
|
|
|
|
# symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org`
|
|
mkdir -p _apis
|
|
ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org
|
|
```
|
|
|
|
**Advanced Installation with `bower`**
|
|
|
|
```bash
|
|
# Install to bower_components
|
|
bower install oauth3
|
|
|
|
|
|
# create a `_apis` folder and an `assets` folder
|
|
mkdir -p _apis assets
|
|
|
|
|
|
# symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org`
|
|
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
|
|
|
|
|
|
# symlink `assets/oauth3.org` to `bower_components/oauth3.org`
|
|
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
|
|
ln -sf ../bower_components/oauth3.org assets/oauth3.org
|
|
```
|
|
|
|
Usage
|
|
-----
|
|
|
|
Update your HTML to include the the following script tag:
|
|
|
|
```html
|
|
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
|
```
|
|
|
|
You can create a very simple demo application like this:
|
|
|
|
```javascript
|
|
var providerUri;
|
|
var opts = { client_uri: OAUTH3.utils.clientUri(window.location) };
|
|
|
|
|
|
// this is any OAuth3-compatible provider, such as oauth3.org
|
|
// in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc
|
|
//
|
|
function onChangeProvider(_providerUri) {
|
|
providerUri = _providerUri;
|
|
return OAUTH3.discover(providerUri, opts); // just to cache
|
|
}
|
|
|
|
|
|
// This opens up the login window for the specified provider
|
|
//
|
|
function onClickLogin() {
|
|
|
|
return OAUTH3.implicitGrant(providerUri, opts).then(function (session) {
|
|
|
|
console.info('Authentication was Successful:');
|
|
console.log(session);
|
|
|
|
// You can use the PPID (or preferably a hash of it) as the login for your app
|
|
// (it securely functions as both username and password which is known only by your app)
|
|
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
|
//
|
|
console.info('Secure PPID (aka subject):', session.token.sub);
|
|
|
|
return OAUTH3.request({
|
|
url: 'https://oauth3.org/api/issuer@oauth3.org/inspect_token'
|
|
, session: session
|
|
}).then(function (resp) {
|
|
|
|
console.info("Inspect Token:");
|
|
console.log(resp.data);
|
|
|
|
});
|
|
|
|
}, function (err) {
|
|
console.error('Authentication Failed:');
|
|
console.log(err);
|
|
});
|
|
|
|
}
|
|
|
|
// initialize the provider to be oauth3.org (or any compatible provider)
|
|
//
|
|
onChangeProvider('oauth3.org');
|
|
```
|
|
|
|
A user's e-mail can be passed into the clientParams object as `clientParams.subject`.
|
|
|
|
To auto-populate the e-mail input of the login popup, make sure the input has the class `js-oauth3-email`.
|
|
|
|
Example:
|
|
```js
|
|
if (clientParams.subject) {
|
|
$('.js-oauth3-email').val(clientParams.subject);
|
|
$('.js-authn-show').prop('disabled', false);
|
|
}
|
|
```
|
|
|
|
### Compatibility with Frameworks and Libraries
|
|
|
|
**jQuery**:
|
|
|
|
You're all set. Nothing else is needed.
|
|
|
|
**Angular 1**:
|
|
|
|
We've created an `Oauth3` service just for you:
|
|
|
|
```html
|
|
<script src="assets/oauth3.org/oauth3.ng.js"></script>
|
|
```
|
|
|
|
```js
|
|
// Require the module as 'oauth3.org'
|
|
var app = angular.module('myAppName', [ 'ui.router', 'oauth3.org' ]);
|
|
|
|
// Require services and other submodules in the form {modulename}@oauth3.org
|
|
app.controller('authCtrl', [ '$scope', 'azp@oauth3.org', function ($scope, Oauth3) { /* ... */ } ]);
|
|
|
|
// For backwards compatibility with older angular applications that rely on string-name introspection
|
|
// you can also use the camel case version of the names in the format {Modulename}Oauth3
|
|
app.controller('authCtrl', function ($scope, AzpOauth3) { /* ... */ });
|
|
```
|
|
|
|
You can include that in addition to the standard file or,
|
|
if you don't want an extra request, just paste it into your `app.js`.
|
|
|
|
Simple API
|
|
----------
|
|
|
|
We include a small wrapper function of just a few lines in the bottom of `oauth3.core.js`
|
|
which exposes a `create` method to make using the underlying library require typing fewer keystrokes.
|
|
|
|
```
|
|
oauth3 = OAUTH3.create(location); // takes a location object, such as window.location
|
|
// to create the Client URI (your app's id)
|
|
// and save it to an internal state
|
|
|
|
promise = oauth3.init(opts); // set and fetch your own site/app's configuration details
|
|
// promises your site's config // opts = { location, session, issuer, audience }
|
|
|
|
promise = oauth3.setIdentityProvider(url); // changes the Identity Provider URI (the site you're logging into),
|
|
// promises the provider's config // gets the config for that site (from their _apis/oauth3.org),
|
|
// and caches it in internal state as the default
|
|
|
|
promise = oauth3.setResourceProvider(url); // changes the Resource Provider URI (the site you're getting stuff from)
|
|
|
|
promise = oauth3.setProvider(url); // changes the both Identity and Resource Provider URI together
|
|
|
|
promise = oauth3.authenticate(); // opens login window for the provider and returns a session
|
|
// (must be called after the setIdentityProvider promise has completed)
|
|
|
|
promise = oauth3.authorize(permissions); // authenticates (if not authenticated) and opens a window to
|
|
// authorize a particular scope (contacts, photos, whatever)
|
|
|
|
promise = oauth3.request({ url, method, data }); // make an (authorized) arbitrary request to an audience's resource
|
|
// (contacts, photos, whatever)
|
|
|
|
promise = oauth3.api(apiname, opts); // make an (authorized) well-known api call to an audience
|
|
// Ex: oauth3.api('dns.list', { sld: 'example', tld: 'com' });
|
|
|
|
// TODO
|
|
api = await oauth3.package(audience, schemaname); // make an (authorized) well-known api call to an audience
|
|
// Ex: api = await oauth3.package('domains.example.com', 'dns@oauth3.org');
|
|
// api.list({ sld: 'mydomain', tld: 'com' });
|
|
|
|
|
|
promise = oauth3.logout(); // opens logout window for the provider
|
|
|
|
oauth3.session(); // returns the current session, if any
|
|
```
|
|
|
|
<!-- TODO
|
|
Track down the old https://labs.daplie.com/docs/ for API schemas
|
|
--
|
|
|
|
|
|
Real API
|
|
----------
|
|
|
|
<!-- hooks -->
|
|
|
|
```
|
|
OAUTH3.clientUri(window.location); // produces the default `client_uri` of your app (also used as `client_id`)
|
|
|
|
OAUTH3.discover(providerUri, { client_id: clientUri }); // Promises the config file for the provider and caches it in memory.
|
|
|
|
OAUTH3.implicitGrant(providerUri, { client_id: clientUri }) // returns a `session` with `session.token.sub` as the secure ppid.
|
|
// debug: true - will cause the windows to not refresh automatically
|
|
// windowType: 'popup' - will use a popup window to ask user for new permissions, if any
|
|
// windowType: 'background' - will automatically log the user in (if all permissions have been accepted)
|
|
|
|
OAUTH3.request({ method: 'GET', url: '', session: '', data: '' }) // make an authenticated request to a resource
|
|
|
|
OAUTH3.logout(providerUri, { client_id: clientUri, session: session }) // opens a popup to confirm logout from the provider
|
|
// Note: you should probably clear your own storage (i.e. localStorage, indexedDb) whenever you call this
|
|
|
|
OAUTH3.urls
|
|
.discover(providerUri, { client_id: clientUri }) // generates a correctly parameterized url
|
|
.implicitGrant(directives, { client_id: clientUri }) // generates a correctly parameterized url
|
|
.refreshToken(directives, opts) // generates a correctly parameterized url
|
|
// opts.client_id = clientUri
|
|
// opts.access_token = <jwt>
|
|
// opts.refresh_token = <jwt>
|
|
```
|
|
|
|
<!-- TODO implicit grant broker -->
|
|
<!-- TODO logout specific user -->
|
|
<!-- TODO request(providerUri, opts) -->
|
|
<!-- TODO login/logout(directives, opts) ? -->
|
|
|
|
Core API (staging)
|
|
----------
|
|
|
|
These APIs are NOT yet public, stable APIs, but they are good to be aware of
|
|
and may help with debugging.
|
|
|
|
DO NOT rely on them. Many of them WILL change (we just wanted to publish with things as they are).
|
|
|
|
Public utilities for browser and node.js:
|
|
|
|
```
|
|
OAUTH3.jwt
|
|
.decode('<urlSafeBase64-encoded-json-web-token>'); // { iat, iss, aud, sub, exp, ttl }
|
|
|
|
OAUTH3
|
|
.query.stringify({ access_token: '...', debug: true }); // access_token=...&debug=true
|
|
.scope.stringify([ 'profile', 'contacts' ]); // 'profile,contacts'
|
|
.uri.normalize('https://oauth3.org/connect/'); // 'oauth3.org/connect'
|
|
.url.normalize('oauth3.org/connect/'); // 'https://oauth3.org/connect'
|
|
.url.resolve('oauth3.org/connect/', '/api/'); // 'https://oauth3.org/connect/api'
|
|
```
|
|
|
|
Issuer API (staging)
|
|
-------------------
|
|
|
|
These additional methods are
|
|
|
|
```
|
|
OAUTH3
|
|
.query.parse('#/?foo=bar&baz=qux'); // { access_token: '...', debug: 'true' }
|
|
.scope.parse('profile,contacts'); // [ 'profile', 'contacts' ]
|
|
.url.redirect(clientParams, grants, tokenOrError); // securely redirect to client (or give security or other error)
|
|
```
|
|
|
|
Internal API
|
|
------------
|
|
|
|
This APIs will absolutely change before they are made public
|
|
(at the very least the leading `_` will be removed)
|
|
|
|
```
|
|
OAUTH3.jwt
|
|
.freshness(tokenMeta, staletimeSeconds, _now); // returns 'fresh', 'stale', or 'expired' (by seconds before expiry / ttl)
|
|
|
|
OAUTH3
|
|
.url._normalizePath('oauth3.org/connect/'); // 'oauth3.org/connect'
|
|
.randomState(); // a 128-bit crypto-random string
|
|
._insecureRandomState(); // a fallback for randomState() in old browsers
|
|
._base64.atob('<non-urlsafe-base64-string>'); // '<binary-string>' (typically json ascii)
|
|
._base64.decodeUrlSafe(b64); // makes base64 safe for window.atob and then calls atob
|
|
|
|
OAUTH3._browser // a collection of things a browser needs to perform requests
|
|
```
|
|
|
|
Roadmap
|
|
-------
|
|
|
|
* v1.0 - "implicit grant" authorization with examples
|
|
* popup
|
|
* iframe
|
|
* documentation
|
|
* v1.1 - cleanup
|
|
* in-flow discovery
|
|
* smallest possible size
|
|
* inline windowing (non-promisable callback)
|
|
* async set/get
|
|
* logout
|
|
* v1.2 - features
|
|
* "authorization code" flow
|
|
* "broker" flow
|
|
* v1.3 - features
|
|
* remove grants
|
|
|
|
URL generation:
|
|
|
|
* `authorizationCode`
|
|
* `authorizationRedirect`
|
|
* `implicitGrant`
|
|
* `loginCode`
|
|
* `resourceOwnerPassword`
|
|
|
|
|
|
|
|
URI vs URL
|
|
----------
|
|
|
|
See <https://danielmiessler.com/study/url-uri/#gs.=MngfAk>
|
|
|
|
Since we do not require the `protocol` to be specified, it is a URI
|
|
|
|
However, we do have a problem of disambiguation since a URI may look like a `path`:
|
|
|
|
1. https://example.com/api/issuer@oauth3.org
|
|
2. example.com/api/issuer@oauth3.org/ (not unique)
|
|
3. /api/issuer@oauth3.org
|
|
4. api/issuer@oauth3.org (not unique)
|
|
|
|
Therefore anywhere a URI or a Path could be used, the URI must be a URL.
|
|
We eliminate #2.
|
|
|
|
As a general rule I don't like rules that sometimes apply and sometimes don't,
|
|
so I may need to rethink this. However, there are cases where including the protocol
|
|
can be very ugly and confusing and we definitely need to allow relative paths.
|
|
|
|
A potential work-around would be to assume all paths are relative (eliminate #4 instead)
|
|
and have the path always key off of the base URL - if oauth3 directives are to be found at
|
|
https://example.com/username/_apis/oauth3.org/index.json then /api/whatever would refer
|
|
to https://example.com/username/api/whatever.
|