1. Overview
Bookshare has a variety of resources available through the API. Some of them are available publicly with no authentication needed, but for many resources, such as copyrighted material or user account data, the Bookshare API v2 requires that a user be authenticated and that their access to a particular resource be authorized. The process of authenticating and authorizing revolves around the concept of a token that is first obtained and then presented to the API on each call. This process and the token follow the OAuth 2 standard, so if you are familiar with that from other contexts or APIs, you should notice many similarities in Bookshare’s API.
2. OAuth 2 tokens
An OAuth 2 access token is a string that is opaque to an API client, but carries meaning for the API server. It represents an authenticated user and the authorization they gave for a specific client to access resources on their behalf. Being opaque means that it doesn’t reveal any identifying information about the user to the client, and because the process of obtaining that token is done outside the view of the client, as we will explain below, it doesn’t reveal any user credentials to the client either. That said, the access token should still be kept private to the client to prevent others from using it. If it were leaked, someone could use it to impersonate that user and that client. It is for this reason that access tokens have a limited lifespan, and can also be revoked on demand, something also explained more below.
For example, a token string of 'aX93nd02k…' might represent the authorization that User A gave for Client X to act on their behalf, after User A was verified by offering their credentials to an authorization server.
To use an access token, you present it on each call that is made on a user’s behalf as a "Bearer" token in an "Authorization" request header.
Using curl as an example, a call to get a list of a user’s reading lists would look like this:
curl -H "Authorization: Bearer <token string>" https://api.bookshare.org/v2/mylists?api_key=<your API key>
This example highlights another authorization feature, which is that an access token is different than an API key. An access token identifies a user and an API key identifies a client. An API key can be used to serve many different users, and a user can obtain an access token for many different API keys. Both of them are important to ways that the Bookshare API secures access to protected resources but they are different concepts and managed differently. The information on this page is focused solely on access tokens.
While the OAuth2 Bearer Token Usage guidelines say that an access token can be presented in request parameters or form parameters, those uses are less secure and are discouraged. The authorization header should always be used as the Bookshare API may remove support for request parameter and form parameter use of access tokens in the future. |
3. Obtaining an OAuth 2 token
The OAuth 2 spec describes different ways, called "grant types", to get a token, with the most common one proceeding like this:
-
Your app: Post a request to the Bookshare Auth site, requesting a token for a certain user, for your app.
-
User: Is presented with a Bookshare page to authorize or deny your app to perform certain operations on their behalf (download books, update account information, etc).
-
The Bookshare Auth site may first challenge the user for their username and password if it can’t authenticate them from the initial request.
-
-
Your app: If the user approved the request, the Bookshare Auth site will redirect to a URI that you provided for your app, with a code included on the response. Your app calls the Bookshare Auth site again to exchange this code for an access token to be used for calls on behalf of that user.
This means a couple of important things:
-
Your app never sees a user’s Bookshare username and password. All you need is the user’s token, which identifies them to Bookshare.
-
Your app needs to be able to be addressed by a URI so that the Bookshare Auth site can deliver the token to you. This URI could be a custom URI scheme registered with a mobile OS, or an HTTP URL handled by your app or site. See the Mobile Development section for more information if you’re developing a mobile application.
The Bookshare API v1 uses a different, custom authentication scheme. To help smooth the migration to the v2 API, OAuth 2 tokens can be used for access to both v1 and v2 resources. |
4. OAuth 2 Grant Types
The Bookshare API supports the "authorization code" grant type.[1] [2] This is the only grant type defined for applications that are operating on behalf of end users. The flow is as described above, and a more concrete example follows.
The authorization code flow also provides a refresh token in its response, which allows you to request a new access token on the user’s behalf when the access token expires, without having to involve the user in reauthorizing your app.
5. Sample OAuth 2 authorization code grant flow
Let’s suppose that the application Flubber wants to get an OAuth 2 token for the person using their application, and wants to use the authorization code grant type.
To be able to handle the callback that will notify them that a user has approved them, the Flubber developers have registered a URI scheme of "flubber" with their mobile app platform, and have implemented the ability to recognize calls to the URI "flubber://authorize".
They have also registered their application with Bookshare, and were given an API key of "abcdefg", and a client secret of "xyz123".[3] In the OAuth 2 flows, these will be used for the client_id
and client_secret
parameter whenever these are called for.
They start by offering the app user a choice to "Login with Bookshare", which results in a GET to the Bookshare authorization server with a collection of request data. They also choose to send along a state
parameter of "something", which they can compare against the state
value that comes back on the redirect. The request would look like this:
GET /oauth/authorize HTTP/1.1
Host: auth.bookshare.org
Content-Type: application/x-www-form-urlencoded
response_type=code&client_id=abcdefg&redirect_uri=flubber://authorize&scope=basic&state=something
The result of this will be a redirect to a Bookshare web page, where the user will enter their credentials, and then be asked to approve or deny Flubber’s access to Bookshare on their behalf. The Flubber app isn’t involved in any of this interaction, so it is irrelevant to them how it happens.
Once the user has authenticated themselves and approved access, the Bookshare authorization server will send a redirect to the Flubber user-agent, giving it the redirect URI that was passed in, and attaching the authorization code.
flubber://authorize?code=4hMt0A&state=something
That authorization code is not the access token, but it can then be exchanged for an access token by calling the API again.
POST /oauth/token HTTP/1.1
Host: auth.bookshare.org
Authorization: Basic abcdefghijklmnop (1)
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=4hMt0A&redirect_uri=flubber://authorize&scope=basic (2)
1 | The basic auth credentials are the client ID and client secret. |
2 | The original redirect URI is needed as well, to ensure that the request is coming from the same client who requested the code. |
This will send a JSON response that will include the access token, expiration time in seconds, and a refresh token.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Content-Length: 169
{
"access_token":"1234567",
"token_type":"bearer",
"refresh_token":"9876543",
"expires_in":1209599,
"scope":"basic"
}
The Flubber app should securely store this access token value and use it in subsequent calls for this user. The app could also choose to securely store the refresh token to use when the access token expires, as shown below.
In curl
, a call to get the user’s reading lists would now look like this:
curl -H "Authorization: Bearer 1234567" https://api.bookshare.org/v2/mylists?api_key=abcdefg
Finally, if the user instead had denied the access request, the Flubber app would still get a response at the redirect URI, but with an error code and reason in it.
flubber://authorize?error=access_denied&error_description=User denied access
Regardless of the reason, without an access token, the Flubber app would now be limited to making requests for unsecured resources on behalf of this user.
6. Token lifespan
A token will be valid until it either expires or is revoked by the user. The expiration time is given, in seconds, in the callback URI or token response. In practice, you will also be told when it expires by an error response if an outdated token is used.
{"error":"unauthorized","error_description":"Access token expired: 1234567"}
If you have a refresh token, you can simply make a call without involving the user, and receive a new access token.
POST /oauth/token HTTP/1.1
Host: auth.bookshare.org
Authorization: Basic abcdefghijklmnop
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=9876544&scope=basic
Revoking a token can be done by a user or a client. A user would revoke it through an interface that might show them all the authorizations they have given (something not yet built by Bookshare), or through an interface provided by a client to let them 'Sign out', for example. At the moment, Bookshare supports revoking a single token at a time.
7. Mobile Development
If you are using the Bookshare API and authorization server for developing mobile applications, you may need to claim a URL pattern or register a custom URL scheme. This will allow the operating system to launch your app upon authorization redirect instead of the system’s browser. The URL can then be parsed and the token retrieved from the redirect URL in order to interact with the Bookshare API.
The process of claiming a URL pattern or registering a custom URL scheme will be dependent on the app’s operating system. Here are some resources on how to deal with OAuth redirect URLs in mobile development:
8. Endpoints
8.1. OAuth 2
There are only two endpoints needed to use OAuth 2: requesting an authorization (a Bookshare user's approval to act on their behalf), and request an access token (a representation of that approval). Which one, or ones, that you use will depend on the grant type flow.
8.1.1. Request client authorization for a user
GET /oauth/authorize
Description
The Bookshare API supports the authorization code grant type, where the end user is asked to authorize access after they have identified themselves correctly to Bookshare. The client ends up with an access token without needing to know the end users Bookshare credentials. See the OAuth 2 spec for more information.
Parameters
Form Parameters
Name | Description | Required | Schema | Default |
---|---|---|---|---|
response_type |
OAuth 2 grant type, should be 'code' for authorization code flow |
X |
enum [code] |
null |
client_id |
Your client identifier |
X |
String |
null |
redirect_uri |
URI for redirect to your client after the user has been authorized |
X |
String |
null |
scope |
The requested scope of the access request. Should be 'basic' |
X |
enum [basic] |
null |
state |
An opaque value that will be passed back with the redirect URI to allow the client to ensure the integrity of the response |
- |
String |
null |
Responses
HTTP Code | Message | Datatype |
---|---|---|
200 |
OAuth 2 token response |
|
0 |
Unexpected error |
Example HTTP request
See the Sample OAuth 2 authorization code grant flow for examples of using this endpoint.
8.1.2. Revoke an OAuth2 access token
POST /oauth/revocations
Description
This endpoint allows you to revoke an OAuth2 access token.
Parameters
Form Parameters
Name | Description | Required | Schema | Default |
---|---|---|---|---|
token |
The token to revoke. |
X |
String |
null |
grant_type |
OAuth 2 grant type of the token to be revoked. |
- |
enum [authorization_code, refresh_token, password] |
null |
Responses
HTTP Code | Message | Datatype |
---|---|---|
200 |
Revocation success message |
|
0 |
Unexpected error |
Example HTTP request
POST /oauth/revocations HTTP/1.1
Host: auth.bookshare.org
Authorization: Basic abcdefghijklmnop
Content-Type: application/x-www-form-urlencoded
token=ad13cb0b-1f5d-44e3-95e4-a072c5200c4f
Example HTTP response
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Content-Length: 169
{
"key": "SUCCESS",
"messages": [
"Token revoked."
]
}
Example Curl request
$ curl -u "abcdefg:" --data-urlencode "token=ad13cb0b-1f5d-44e3-95e4-a072c5200c4f" https://auth.bookshare.org/oauth/revocations -X POST
8.1.3. Request an OAuth 2 access token
POST /oauth/token
Description
This endpoint allows you to exchange either an authorization code or a refresh token for an access token.
Parameters
Form Parameters
Name | Description | Required | Schema | Default |
---|---|---|---|---|
grant_type |
OAuth 2 grant type, should be 'authorization_code', 'refresh_token'. The 'password' type is limited to trusted clients only. |
X |
enum [authorization_code, refresh_token, password] |
null |
code |
Authorization code from authorization request (only for 'authorization_code' type) |
- |
String |
null |
refresh_token |
Refresh token from original token request (only for 'refresh_token' type) |
- |
String |
null |
username |
Bookshare username (only for 'password' type) |
- |
String |
null |
password |
Bookshare password (only for 'password' type). Since passwords may contain special characters, be sure to URL-encode the data that is submitted. |
- |
String |
null |
scope |
The requested scope of the authorization (optional) |
- |
enum [basic] |
[basic] |
Responses
HTTP Code | Message | Datatype |
---|---|---|
200 |
OAuth 2 token response |
|
0 |
Unexpected error |
Example HTTP request
POST /oauth/token HTTP/1.1
Host: auth.bookshare.org
Authorization: Basic abcdefghijklmnop
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=12345&scope=basic
Example HTTP response
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Content-Length: 169
{
"access_token":"ad13cb0b-1f5d-44e3-95e4-a072c5200c4f",
"token_type":"bearer",
"refresh_token":"3303fab2-2823-4560-a710-eb1f11e544e7",
"expires_in":1209599,
"scope":"basic"
}
Example Curl request
$ curl -u "abcdefg:" --data-urlencode "grant_type=authorization_code&code=12345&scope=basic" https://auth.bookshare.org/oauth/token
9. Models
9.1. Status
Field Name | Description | Type | Format | Nullable |
---|---|---|---|---|
key |
String |
|||
messages |
List of String |
X |
9.2. Token
Field Name | Description | Type | Format | Nullable |
---|---|---|---|---|
access_token |
An OAuth 2 bearer token |
String |
||
token_type |
Will always be 'bearer' |
String |
||
expires_in |
Number of seconds that this token will be valid |
String |
||
refresh_token |
Only for the 'code' grant type, this token can be redeemed for another access token without requiring the user to reauthorize the application. |
String |
||
scope |
A name representing the set of capabilities for which this token is authorized |
String |
9.3. Token Error
Field Name | Description | Type | Format | Nullable |
---|---|---|---|---|
error |
One of a set of standard error names |
String |
enum [invalid_request, unauthorized_client, access_denied, unsupported_response_type, invalid_scope, server_error, temporarily_unavailable, invalid_grant] |
|
error_description |
A human readable description of the error condition |
String |
Appendix A: OAuth2 Password Grant Type
For trusted clients, the Bookshare API supports the OAuth2 Password Grant Type, offering a simplified way of getting authorization tokens for users. We limit its use because it is a flow that allows the client to see a user’s password before it is transmitted to Bookshare. Before we allow a client to use the password grant type, we ask the following:
-
To demonstrate that their system does not store the plaintext password, or forward to any third party.
-
To describe how the authorization code or implicit grant types are not possible with their technology.
Our goal is to provide as strong of assurances as is reasonable to the Bookshare users that their credentials are being managed responsibly. The password grant type requires a significant amount of trust between Bookshare and a client, so the stronger the demonstration of your credentials management, the more likely we will be to consider allowing this grant type.
A.1. Password Grant Flow
The password grant type is similar to the HTTP Basic Auth flow, in that the client collects and transmits a user’s credentials, and gets an authorization answer directly. In this case, the client gets the OAuth2 token back after this first call, without the redirection required in the authorization code or implicit grant types.
For example, a client will collect the username and password of a user through a form or some other means, and then call the token endpoint. Along with this information, the client should pass a HTTP Basic Auth header, with the API key as the username and a blank string as the password. The way this data is formed and presented will vary by programming language and tool, but an example with curl
would look like this:
curl -u "abcdefg:" -d "grant_type=password&username=john.smith@somewhere.org&password=mysecret" https://auth.bookshare.org/oauth/token
The HTTP request itself will look something like this:
POST /oauth/token HTTP/1.1
Host: auth.bookshare.org
Authorization: Basic eXR2czlwenNkNjJidjdyemFtd2RrdGhlOg==
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=john.smith@somewhere.org&password=abcdefg
The Bookshare server will authenticate the user, and if their credentials match, will return a token. This will be the same kind of access token you would get from the other grant types, with an expiration interval and a refresh token.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Content-Length: 169
{
"access_token":"1234567",
"token_type":"bearer",
"refresh_token":"9876543",
"expires_in":1209599,
"scope":"basic"
}
If the credentials don’t match what the server knows about the user, you will get a 400 status code and an error message.
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
{
"error":"invalid_grant",
"error_description":"Bad credentials"
}
Once you have an access token, you can store it for this user, and pass it along with each subsequent request to identify the user to the system. In curl
, a call to get the user’s reading lists, for example, would look like this:
curl -H "Authorization: Bearer 1234567" https://api.bookshare.org/v2/mylists?api_key=abcdefg