OAuth PKCE Grant provider for byu-browser-oauth.
For questions or issues, try the issue tracker or the Web Community Slack.
In a normal OAuth authentication flow, the client calls the authentication server with a client id and a client secret, which are roughly analagous to a username and password. When working with distributed clients, such as Javascript-based browser applications, we can't use the client secret, as that would expose it to the world and allow anyone to make calls as our application.
PKCE Grant aims to solve this problem by using your application's URL instead of the secret. The theory is that, because URLs are guaranteed to be unique, no other application can impersonate yours.
When you register your application with the OAuth server, you must provide a 'callback URL'. Note that this does NOT mean that you must register every possible URL in your application - you just need to register one path in your application that the OAuth server will send users to after logging them in. Generally, this is the root URL of your application, but you can use any other URL that is unique to your app.
For non-production environments, you should always use a 'Sandbox' client ID. For the production environment, use the 'Production' key.
You can register your application and callback URLs in the api.byu.edu/store. For each environment in your application (prod, nonprod, local, etc.), you must take the following steps:
- Create an application
- Generate keys (even though you only need the client ID.)
The PKCE Grant OAuth provider is available from the Web Community CDN. It is preferred that you not bundle the provider into your application, so that you can benefit from updates to the provider (including security updates) without changing your application. The Web Community CDN is built to be fast and efficient, and serves millions of requests reliably.
The provider is distributed as a Javascript Modules. ES Modules are supported by all browsers that the Web Community supports (latest 2 versions of Chrome, Safari, Firefox, and Edge). There is also a non-module build available, though support for it may be limited.
The easiest way to import the provider into your application is to import it in the <head>
of your page. If you are building your application to output ES Modules, you can also choose to import
it into your application's main Javascript files. However, this will not work if you build transpile
your application for use on browsers that do not support modules, so it's probably easiest to put
it in your <head>
.
However you import it, the Javascript you use to initialize the provider will be similar:
import * as pkce from 'https://cdn.byu.edu/browser-oauth-pkce/latest/pkce-grant.min.js';
pkce.configure({
// your configuration here (see below)
});
Your configuration block can take one of two forms:
- URL-to-configuration mappings (recommended)
- Plain configuration object (only if you know what you're doing)
The main configuration object has the following options:
Name | Type | Default | Description |
---|---|---|---|
clientId | String | None (required) | The OAuth client ID |
callbackUrl | URL String | Current URL | The callback URL registered to your application |
autoRefreshOnTimeout | Boolean | false | Whether to try to automatically refresh the user's session when it expires |
logoutRedirect | URL String | Current URL | Where the user's browser should redirect after completing logout process |
issuer | URL String | https://api.byu.edu | The OAuth issuer to use. Do not change this unless you know what you are doing. |
baseUrl | URL String | https://api.byu.edu | The base URL for authentication. Use "https://api-sandbox.byu.edu" for Sandbox |
const config = {
clientId: 'client ID',
callbackUrl: 'http://my-app.byu.edu',
autoRefreshOnTimeout: true
};
If you know exactly what settings should be used for this environment of your
application, you can pass this object directly to configure
. Otherwise, you should
use the URL-matching configuration.
In this form, you map URL patterns to configuration objects.
const config = {
'https://my-app.byu.edu': {
clientId: 'production client ID'
},
'https://stg.my-app.byu.edu': {
clientId: 'stage client ID'
},
'http://localhost:8080': {
clientId: 'local development client ID'
}
};
With the URL mappings, you specify what your settings are (including client IDs) for different URLs. The provider will select the appropriate configuration for the current page, matching the most-specific URL that is a subset of the current page's URL.
So, if the current page is https://example.com/my/application/nested/path
, the following
patterns will match:
https://example.com
https://example.com/
https://example.com/my
https://example.com/my/
https://example.com/my/application
https://example.com/my/application/
https://example.com/my/application/nested
https://example.com/my/application/nested/
https://example.com/my/application/nested/path
These will not match:
http://example.com
(http
instead ofhttps
)https://subdomain.example.com
(domain name must match exactly)https://example.com/my-application
(wrong path)https://example.com/my/application/nested/path/stuff
(too specific of a path)
The provider will always select the most specific URL pattern,
so if the browser is at https://example.com/my/application/route
, then a pattern of
https://example.com/my/application
will be preferred, even if there are configurations
for https://example.com/my
and https://example.com
.
Note that, for purposes of resolving configurations, all query (?
), fragment (#
), and matrix (;
)
parameters are ignored.
<head>
<script type="module">
import * as pkce from 'https://cdn.byu.edu/browser-oauth-pkce/latest/pkce-grant.min.js';
pkce.configure({
'https://my-app.byu.edu': { clientId: '{production key}' },
'https://stg.my-app.byu.edu': { clientId: '{stage key}' },
'http://localhost:8080': { clientId: '{local development key}' }
});
</script>
</head>
In order to get the authentication status, user information, and OAuth tokens, you can use a AuthenticationObserver. This is a provider-agnostic interface for handling OAuth sessions and user information.
For detailed, authoritative documentation, visit https://github.com/byuweb/byu-browser-oauth.
Unlike the PKCE grant provider, the AuthenticationObserver API is installed from NPM:
npm install --save @byuweb/browser-oauth
Or, if you use Yarn:
yarn add @byuweb/browser-oauth
In your Javascript code, you can then import the module and create an AuthenticationObserver:
// This import may need to be adjusted, depending on how you build your application
import { AuthenticationObserver } from '@byuweb/browser-oauth';
// If using Node-style imports: `const { AuthenticationObserver } = require('@byuweb/browser-oauth');
const observer = new AuthenticationObserver(({state, token, user, error}) => {
// React to the change in state
if (error) {
// React to authentication error
} else if (token && user) {
// User is logged-in - start loading data or take other actions
} else {
// No user is logged in, but there has not been an error
}
});
Every time the authentication state changes, your observer will receive a callback.
The state
parameter contains a string value describing the current authentication state.
You usually won't need to use this.
If the user is logged in, the token
and user
parameters will be set.
If set, token
contains the current OAuth token.
If set, user
contains information about the logged-in user.
If an error occurs in the login process, the error
parameter will be set, containing the
error. Generally, if there is an error, neither token
nor user
will be set.
In order to log the user in, you must import and call the login
and logout
functions:
import { login, logout } from '@byuweb/browser-oauth';
// If using Node-style imports: `const { login, logout } = require('@byuweb/browser-oauth');
// When you want to log the user in:
login().then(({state, token, user, error}) => {
// If we don't have to redirect the browser to log the user in, you can respond to the completed
// login here
});
// Similarly, for logout:
logout().then(({state, token, user, error}) => {
// If we don't have to redirect the browser to log the user in, you can respond to the completed
// login here
});
If you application requires that users always be logged in, add this after instantiating
your AuthenticationObserver
:
import { AuthenticationObserver, login, STATE_UNAUTHENTICATED } from '@byuweb/browser-oauth';
const observer = new AuthenticationObserver(({state, token, user, error}) => {
// React to the change in state
if (error) {
// React to authentication error
} else if (token && user) {
// User is logged-in - start loading data or taking other actions
} else {
if (state == STATE_UNAUTHENTICATED) {
// User is not logged in AND authentication process has NOT already begun
login();
}
}
});
A simpler way to require logins is coming soon.
If you wish to stop receiving notifications about authentication events,
call disconnect()
on AuthenticationObserver
.
To turn on debug logging, add the following Javascript snippet before you initialize the PKCE grant provider:
(window.byuOAuth = window.byuOAuth || {}).logging = 'debug';
You can also add an attribute to your page's <html>
element. You do not need to do both.
<!DOCTYPE html>
<html lang="en" byu-oauth-logging="debug">
<head></head>
<body></body>
</html>