Skip to content

Commit a8ff95f

Browse files
author
Grace Yim
committed
Merge 4343e29 into 5d25b67
2 parents 5d25b67 + 4343e29 commit a8ff95f

32 files changed

+3071
-1335
lines changed

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ To run the tests enter:
1616
```shell
1717
$ mvn test
1818
```
19+
20+
To get coverage reports with the JaCoCo Maven Plugin:
21+
```shell
22+
$ mvn clean verify -P all-tests
23+
$ open target/site/jacoco/index.html
24+
```

README-Iframe.md

Lines changed: 14 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
11
# Authenticating using the Toopher `iframe`
22

3-
Toopher's `iframe`-based authentication flow is the simplest way for web developers to integrate Toopher Two-Factor Authentication into an application.
3+
Toopher's `<iframe>`-based authentication flow is the simplest way for web developers to integrate Toopher Two-Factor Authentication into an application.
44

5-
## Toopher `iframe` Authentication Overview
5+
## Toopher `<iframe>` Authentication Overview
66

77
The `iframe`-based authentication flow works by inserting an `<iframe>` element into the HTML displayed to the user after a successful username/password validation (but before they are actually logged-in to the service). The `iframe` URL is generated by our library and its content is served from the Toopher API server. The `iframe` guides the user through the process of authenticating with Toopher. Once complete, the `iframe` will return the result of the authentication to your server by `POST`ing the response via HTML to an endpoint of your choice. Your server validates the cryptographic signature using our library which determines whether or not the user successfully authenticated.
88

99
Two distinct `iframe` flows are available:
1010

11-
* **Simple** serve an all-in-one `iframe` that handles *Pairing* and
12-
*Authentication*
11+
* **Simple** serve an all-in-one `iframe` that handles *Pairing* and *Authentication*
1312
* **More flexible** serve the *Pairing* `iframe` and the *Authentication* `iframe` as needed. Note: a *Pairing* request is used to pair a user account with a particular mobile device (this is typically a one-time process); an *Authentication* request is used to authenticate a particular action on behalf of a user
1413

1514
## Authentication Workflow
1615

17-
### Step 0: Primary Authentication
18-
16+
### Primary Authentication
1917
We recommend using some form of primary authentication before initiating a Toopher authentication request. Typical primary authentication methods involve verifying that the user has a valid username and password to access the resource being protected.
2018

2119
### Step 1: Embed a request in an `<iframe>`
22-
2320
After verifying the user's primary authentication, but before Assuming the user's primary authentication checks out, the next step is to kickoff Toopher authentication.
2421

2522
1. Generate a URI by specifying the request parameters to the library as detailed below
2623
1. Display a webpage to your user that embeds this URI within an `<iframe>` element. The markup requirements for the `<iframe>` element are described in the "HTML Markup" section
2724

2825
### Step 2: Validate the result
2926
1. Toopher-iframe results posted back to server
30-
1. Server calls `ToopherIframe.validate()` to verify that result is valid. `.validate()` returns a `Map` of trusted data if the signature is valid, or throws a `SignatureValidationError` if the signature is invalid.
27+
1. Server calls `ToopherIframe.validatePostback()` to verify that result is valid. `.validatePostback()` returns a `Map` of trusted data if the signature is valid, or throws a `SignatureValidationError` if the signature is invalid.
3128
1. The server should check for possible errors returned by the API in the `error_code` map entry
3229
1. If no errors were returned, the result of the authentication is in the `granted` map entry
3330

@@ -47,9 +44,9 @@ There is no difference in the markup required for a Pairing vs. an Authenticatio
4744

4845
### All-in-one iframe
4946

50-
#### Generating an iframe URI
47+
#### Generating an iframe URL
5148

52-
Every Toopher Authentication session should include a unique `requestToken` - a randomized `String` that is included in the signed request to the Toopher API and returned in the signed response from the Toopher `iframe`. To guard against potential replay attacks, your code should validate that the returned `requestToken` is the same one used to create the request.
49+
Every Toopher Authentication session should include a unique `requestToken` - a randomized `String` that is included in the signed request to the Toopher API and returned in the signed response from the Toopher `<iframe>`. To guard against potential replay attacks, your code should validate that the returned `requestToken` is the same one used to create the request.
5350

5451
Creating a random request token and storing it in the server-side session using the Java Servlet API:
5552

@@ -60,11 +57,11 @@ Creating a random request token and storing it in the server-side session using
6057

6158
The Toopher Authentication API provides the requester a rich set of controls over authentication parameters.
6259

63-
String authIframeUrl = iframeApi.authUri(userName, resetEmail, actionName, automationAllowed, challengeRequired, requestToken, requesterMetadata, ttl);
60+
String authIframeUrl = iframeApi.getAuthenticationUrl(userName, resetEmail, requestToken, actionName, requesterMetadata);
6461

65-
For the simple case of authenticating a user at login, a `loginIframeUrl` helper method is available:
62+
For the simple case of authenticating a user at login, a `getAuthenticationUrl` helper method is available:
6663

67-
String loginIframeUrl = iframeApi.loginUri(userName, resetEmail, requestToken)
64+
String loginIframeUrl = iframeApi.getAuthenticationUrl(userName, resetEmail, requestToken)
6865

6966
When the the `iframe` is served this way Toopher will determine if the
7067
input `userName` is already paired; if they have not, we will show the
@@ -80,7 +77,7 @@ In this example, `data` is a `Map<String, String[]>` of the form data POSTed to
8077
request.getSession().removeAttribute("ToopherRequestToken");
8178

8279
try {
83-
Map<String, String> validatedData = iframeApi.validate(data, requestToken);
80+
Map<String, String> validatedData = iframeApi.validatePostback(data, requestToken);
8481
if (validatedData.containsKey("error_code")) {
8582
String errorCode = validatedData.get("error_code");
8683
// There was an error -- user should not be authenticated
@@ -104,78 +101,8 @@ In this example, `data` is a `Map<String, String[]>` of the form data POSTed to
104101
// went wrong (incorrect session token, expired TTL, invalid signature)
105102
}
106103

107-
### Separate Pairing and Authentication iframes
108-
109-
#### Generating an Authentication iframe URI
110-
111-
Every Toopher Authentication session should include a unique `requestToken` - a randomized `String` that is included in the signed request to the Toopher API and returned in the signed response from the Toopher `iframe`. To guard against potential replay attacks, your code should validate that the returned `requestToken` is the same one used to create the request.
112-
113-
Creating a random request token and storing it in the server-side session using the Java Servlet API:
114-
115-
private static final SecureRandom secureRandom = new SecureRandom();
116-
// simple way to generate a randomized string.
117-
String requestToken = new BigInteger(20 * 8, secureRandom).toString(32);
118-
request.getSession().setAttribute("ToopherRequestToken", requestToken);
119-
120-
The Toopher Authentication API provides the requester a rich set of controls over authentication parameters.
121-
122-
String authIframeUrl = iframeApi.authUri(userName, resetEmail, actionName, automationAllowed, challengeRequired, requestToken, requesterMetadata, ttl);
123-
124-
For the simple case of authenticating a user at login, a `loginIframeUrl` helper method is available:
125-
126-
String loginIframeUrl = iframeApi.loginUri(userName, resetEmail, requestToken)
127-
128-
#### Generating a Pairing iframe URI
129-
130-
String pairIframeUrl = iframeApi.pairUri(userName, resetEmail)
104+
### Separate Pairing iframe
131105

132-
#### Validating postback data from Authentication iframe and parsing API errors
106+
#### Generating a Pairing iframe URL
133107

134-
In this example, `data` is a `Map<String, String[]>` of the form data POSTed to your server from the Toopher Authentication `iframe`. You should replace the commented blocks with code appropriate for the condition described in the comment.
135-
136-
Map<String, String[]> data = httpServletRequest.getParameterMap();
137-
String requestToken = (String)request.getSession().getAttribute("ToopherRequestToken");
138-
// invalidate the Request Token to guard against replay attacks
139-
request.getSession().removeAttribute("ToopherRequestToken");
140-
141-
try {
142-
Map<String, String> validatedData = iframeApi.validate(data, requestToken);
143-
if (validatedData.containsKey("error_code")) {
144-
// check for API errors
145-
String errorCode = validatedData.get("error_code");
146-
if (errorCode.equals(ToopherIframe.PAIRING_DEACTIVATED)) {
147-
// User deleted the pairing on their mobile device.
148-
//
149-
// Your server should display a Toopher Pairing iframe so their account can be re-paired
150-
//
151-
} else if (errorCode.equals(ToopherIframe.USER_OPT_OUT)) {
152-
// User has been marked as "Opt-Out" in the Toopher API
153-
//
154-
// If your service allows opt-out, the user should be granted access.
155-
//
156-
} else if (errorCode.equals(ToopherIframe.USER_UNKNOWN)) {
157-
// User has never authenticated with Toopher on this server
158-
//
159-
// Your server should display a Toopher Pairing iframe so their account can be paired
160-
//
161-
}
162-
} else {
163-
// Signature is valid, and no api errors -- check authentication result
164-
boolean authPending = validatedData.get("pending").toLowerCase().equals("true");
165-
boolean authGranted = validatedData.get("granted").toLowerCase().equals("true");
166-
167-
// authenticationResult is the ultimate result of Toopher second-factor authentication
168-
boolean authenticationResult = authGranted && !authPending;
169-
if (authenticationResults) {
170-
// Log user in
171-
} else {
172-
// Fail authentication attempt
173-
}
174-
}
175-
} catch (ToopherIframe.SignatureValidationError e) {
176-
// Signature was invalid -- user should not authenticated
177-
//
178-
// e.getMessage() will return more information about what specifically
179-
// went wrong (incorrect session token, expired TTL, invalid signature)
180-
//
181-
}
108+
String pairIframeUrl = iframeApi.getUserManagementUrl(userName, resetEmail)

README.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Make sure you visit (http://dev.toopher.com) to get acquainted with the Toopher
1616
The first step to accessing the Toopher API is to sign up for an account at the development portal (http://dev.toopher.com) and create a "requester". When that process is complete, your requester is issued OAuth 1.0a credentials in the form of a consumer key and secret. Your key is used to identify your requester when Toopher interacts with your customers, and the secret is used to sign each request so that we know it is generated by you. This library properly formats each request with your credentials automatically.
1717

1818
#### The Toopher Two-Step
19-
Interacting with the Toopher web service involves two steps: pairing, and authenticating.
19+
Interacting with the Toopher web service involves two steps: pairing and authenticating.
2020

2121
##### Pair
2222
Before you can enhance your website's actions with Toopher, your customers will need to pair their phone's Toopher app with your website. To do this, they generate a unique, nonsensical "pairing phrase" from within the app on their phone. You will need to prompt them for a pairing phrase as part of the Toopher enrollment process. Once you have a pairing phrase, just send it to the Toopher API along with your requester credentials and we'll return a pairing ID that you can use whenever you want to authenticate an action for that user.
@@ -34,13 +34,25 @@ import com.toopher.*;
3434
ToopherAPI api = new ToopherAPI("<your consumer key>", "<your consumer secret>");
3535

3636
// Step 1 - Pair with their phone's Toopher app
37-
PairingStatus pairing = api.pair("pairing phrase", "username@yourservice.com");
37+
// With pairing phrase
38+
Pairing pairing = api.pair("username@yourservice.com", "pairing phrase");
39+
// With SMS
40+
Pairing pairing = api.pair("username@yourservice.com", "555-555-5555")
41+
// With QR code
42+
Pairing pairing = api.pair("username@yourservice.com")
3843

3944
// Step 2 - Authenticate a log in
40-
AuthenticationStatus auth = api.authenticate(pairing.id, "my computer");
45+
// With pairingId and terminal name
46+
AuthenticationRequest auth = api.authenticate(pairing.id, "my computer");
47+
// With username and requester specified Id (Returns exception if terminal is not found)
48+
AuthenticationRequest auth = api.authenticate("username", null, "requesterSpecifiedId")
49+
// With username, terminal name and requester specified Id (Returns exception if terminal is not found)
50+
AuthenticationRequest auth = api.authenticate("username", "my computer", "requesterSpecifiedId")
51+
// With username and terminal name (New terminal is created if terminal is not found)
52+
AuthenticationRequest auth = api.authenticate("username", "my computer")
4153

4254
// Once they've responded you can then check the status
43-
AuthenticationStatus status = api.getAuthenticationStatus(auth.id);
55+
auth.refreshFromServer()
4456
if (status.pending == false && status.granted == true) {
4557
// Success!
4658
}
@@ -62,8 +74,17 @@ Alternatively, you can consume this library using Maven:
6274
</dependency>
6375

6476
#### Try it out
65-
Check out `com.toopher.ToopherAPIDemo.java` for an example program that walks you through the whole process! A runnable jar for the demo can be built and executed as follows:
77+
Check out `com.toopher.ToopherAPIDemo.java` for an example program that walks you through the whole process!
78+
79+
###Ant
80+
A runnable jar for the demo can be built and executed in Ant as follows:
6681
```shell
6782
$ ant
6883
$ java -jar dist/toopher-1.0.0.jar
6984
```
85+
86+
###Maven
87+
The demo can be executed in Maven as follows:
88+
```shell
89+
$ mvn exec:java -Dexec.mainClass="com.toopher.ToopherAPIDemo"
90+
```

assets/js/toopher-web.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
frameworkPostArgs = $.parseJSON(frameworkPostArgsJSON);
2424
}
2525
var postData = $.extend({}, msgData.payload, frameworkPostArgs);
26+
var toopherData = {'toopher_iframe_data': $.param(postData)};
27+
2628
if(iframe.attr('use_ajax_postback')){
27-
$.post(iframe.attr('toopher_postback'), postData)
29+
$.post(iframe.attr('toopher_postback'), toopherData)
2830
.done(function(data){
2931
data = $.parseJSON(data);
3032
});
3133
} else {
32-
postToUrl(iframe.attr('toopher_postback'), postData, 'POST');
34+
postToUrl(iframe.attr('toopher_postback'), toopherData, 'POST');
3335
}
3436
}
3537
}

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
<destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile>
8787
<dataFile>${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile>
8888
<excludes>
89-
<exclude>**/ToopherAPIDemo.java</exclude>
89+
<exclude>**/ToopherAPIDemo.class</exclude>
9090
</excludes>
9191
</configuration>
9292
<executions>
@@ -125,7 +125,7 @@
125125
<version>0.7.1.201405082137 </version>
126126
<configuration>
127127
<excludes>
128-
<exclude>**/ToopherAPIDemo.java</exclude>
128+
<exclude>**/ToopherAPIDemo.class</exclude>
129129
</excludes>
130130
</configuration>
131131
<executions>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.toopher;
2+
3+
import org.json.JSONException;
4+
import org.json.JSONObject;
5+
6+
/**
7+
* Created by graceyim on 1/26/15.
8+
*/
9+
public class Action extends ApiResponseObject {
10+
/**
11+
* The unique id for the authentication request
12+
*/
13+
public String id;
14+
15+
/**
16+
* The name of the action
17+
*/
18+
public String name;
19+
20+
public Action(JSONObject json) throws JSONException {
21+
super(json);
22+
23+
this.id = json.getString("id");
24+
this.name = json.getString("name");
25+
}
26+
27+
public void update(JSONObject jsonResponse) throws JSONException {
28+
this.name = jsonResponse.getString("name");
29+
this.updateRawResponse(jsonResponse);
30+
}
31+
}

src/main/java/com/toopher/ApiResponseObject.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@ public class ApiResponseObject {
1111
/**
1212
* A map of the raw API response data
1313
*/
14-
public Map raw;
15-
14+
public Map<String, Object> rawResponse;
15+
1616
public ApiResponseObject(JSONObject json) throws JSONException {
17-
this.raw = jsonToMap(json);
17+
this.rawResponse = jsonToMap(json);
18+
}
19+
20+
public void updateRawResponse(JSONObject json) throws JSONException {
21+
this.rawResponse = jsonToMap(json);
1822
}
1923

20-
private Map<String, Object> jsonToMap(JSONObject json) throws JSONException{
21-
Map<String,Object> result = new HashMap<String,Object>();
22-
23-
for (Iterator<String> i = json.keys(); i.hasNext(); ) {
24-
String key = i.next();
25-
Object o = json.get(key);
26-
result.put(key, o);
27-
}
28-
29-
return result;
24+
private Map<String, Object> jsonToMap(JSONObject json) throws JSONException {
25+
Map<String, Object> result = new HashMap<String, Object>();
26+
27+
Iterator<?> i = json.keys();
28+
while(i.hasNext()){
29+
String key = (String) i.next();
30+
Object o = json.get(key);
31+
result.put(key, o);
32+
}
33+
34+
return result;
3035
}
3136

3237
}

0 commit comments

Comments
 (0)