498 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			498 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)
 | |
| | Sponsored by [Daplie](https://daplie.com)
 | |
| 
 | |
| 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.daplie.com/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 `well-known` into the folder `example.com/`
 | |
| 8. Rename the folder `well-known` to `.well-known` (when you do this, it become invisible, that's okay)
 | |
| 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
 | |
| │
 | |
| │
 | |
| ├── .well-known (hidden)
 | |
| │   └── oauth3
 | |
| │       ├── callback.html
 | |
| │       ├── directives.json
 | |
| │       └── index.html
 | |
| ├── assets
 | |
| │   └── org.oauth3
 | |
| │       └── 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/org.oauth3
 | |
| mkdir -p assets
 | |
| git clone git@git.daplie.com:OAuth3/oauth3.js.git assets/org.oauth3
 | |
| pushd assets/org.oauth3
 | |
| git checkout v1
 | |
| popd
 | |
| 
 | |
| 
 | |
| # symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3`
 | |
| mkdir -p .well-known
 | |
| ln -sf  ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
 | |
| ```
 | |
| 
 | |
| **Advanced Installation with `bower`**
 | |
| 
 | |
| ```bash
 | |
| # Install to bower_components
 | |
| bower install oauth3
 | |
| 
 | |
| 
 | |
| # create a `.well-known` folder and an `assets` folder
 | |
| mkdir -p .well-known assets
 | |
| 
 | |
| 
 | |
| # symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3`
 | |
| ln -sf  ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
 | |
| 
 | |
| 
 | |
| # symlink `assets/org.oauth3` to `bower_components/oauth3`
 | |
| ln -sf  ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
 | |
| ln -sf  ../bower_components/oauth3 assets/org.oauth3
 | |
| ```
 | |
| 
 | |
| Usage
 | |
| -----
 | |
| 
 | |
| Update your HTML to include the the following script tag:
 | |
| 
 | |
| ```html
 | |
| <script src="assets/org.oauth3/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/org.oauth3/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 .well-known/oauth3),
 | |
|                                                     // 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
 | |
|                                                     // See https://labs.daplie.com/docs/ for API schemas
 | |
|                                                     // Ex: oauth3.api('dns.list', { sld: 'daplie', 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.daplie.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
 | |
| ```
 | |
| 
 | |
| 
 | |
| 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/.well-known/oauth3/directives.json then /api/whatever would refer
 | |
| to https://example.com/username/api/whatever.
 |