You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2016/02/19 19:22:17 UTC

[1/2] nifi git commit: NIFI-1497: - Introducing a one time use password service for use in query parameters when accessing UI extensions and downloading resources. - Using one time use tokens when accessing ui extensions and downloading resources. - Ensu

Repository: nifi
Updated Branches:
  refs/heads/master 86ab4428f -> a8edab2e7


http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java
index 414d9f8..23b49b7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java
@@ -26,7 +26,7 @@ import org.apache.nifi.authorization.Authority;
 import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.UntrustedProxyException;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.apache.nifi.web.security.user.NiFiUserDetails;
 import org.junit.Assert;
 import org.junit.Before;
@@ -104,8 +104,8 @@ public class NiFiAuthorizationServiceTest {
         authorizationService.setUserService(userService);
     }
 
-    private NiFiAuthortizationRequestToken createRequestAuthentication(final String... identities) {
-        return new NiFiAuthortizationRequestToken(Arrays.asList(identities));
+    private NiFiAuthorizationRequestToken createRequestAuthentication(final String... identities) {
+        return new NiFiAuthorizationRequestToken(Arrays.asList(identities));
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilterTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilterTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilterTest.java
new file mode 100644
index 0000000..d309191
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilterTest.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.otp;
+
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class OtpAuthenticationFilterTest {
+
+    private final static String UI_EXTENSION_AUTHENTICATED_USER = "ui-extension-token-authenticated-user";
+    private final static String UI_EXTENSION_TOKEN = "ui-extension-token";
+
+    private final static String DOWNLOAD_AUTHENTICATED_USER = "download-token-authenticated-user";
+    private final static String DOWNLOAD_TOKEN = "download-token";
+
+    private OtpService otpService;
+    private OtpAuthenticationFilter otpAuthenticationFilter;
+
+    @Before
+    public void setUp() throws Exception {
+        otpService = mock(OtpService.class);
+        doAnswer(new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                String downloadToken = (String) args[0];
+
+                if (DOWNLOAD_TOKEN.equals(downloadToken)) {
+                    return DOWNLOAD_AUTHENTICATED_USER;
+                }
+
+                throw new OtpAuthenticationException("Invalid token");
+            }
+        }).when(otpService).getAuthenticationFromDownloadToken(anyString());
+        doAnswer(new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                String uiExtensionToken = (String) args[0];
+
+                if (UI_EXTENSION_TOKEN.equals(uiExtensionToken)) {
+                    return UI_EXTENSION_AUTHENTICATED_USER;
+                }
+
+                throw new OtpAuthenticationException("Invalid token");
+            }
+        }).when(otpService).getAuthenticationFromUiExtensionToken(anyString());
+
+        otpAuthenticationFilter = new OtpAuthenticationFilter();
+        otpAuthenticationFilter.setOtpService(otpService);
+    }
+
+    @Test
+    public void testInsecureHttp() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(false);
+        assertNull(otpAuthenticationFilter.attemptAuthentication(request));
+    }
+
+    @Test
+    public void testNoAccessToken() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(null);
+        assertNull(otpAuthenticationFilter.attemptAuthentication(request));
+    }
+
+    @Test(expected = InvalidAuthenticationException.class)
+    public void testTokenSupportDisabled() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn("my-access-token");
+
+        final OtpAuthenticationFilter noTokenAuthenticationFilter = new OtpAuthenticationFilter();
+        noTokenAuthenticationFilter.attemptAuthentication(request);
+    }
+
+    @Test
+    public void testUnsupportedDownloadPath() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn("my-access-token");
+        when(request.getContextPath()).thenReturn("/nifi-api");
+        when(request.getPathInfo()).thenReturn("/controller/config");
+
+        assertNull(otpAuthenticationFilter.attemptAuthentication(request));
+    }
+
+    @Test
+    public void testUiExtensionPath() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(UI_EXTENSION_TOKEN);
+        when(request.getContextPath()).thenReturn("/nifi-update-attribute-ui");
+
+        final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
+        final List<String> chain = result.getChain();
+        assertEquals(1, chain.size());
+        assertEquals(UI_EXTENSION_AUTHENTICATED_USER, chain.get(0));
+
+        verify(otpService, times(1)).getAuthenticationFromUiExtensionToken(UI_EXTENSION_TOKEN);
+        verify(otpService, never()).getAuthenticationFromDownloadToken(anyString());
+    }
+
+    @Test
+    public void testProvenanceInputContentDownload() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
+        when(request.getContextPath()).thenReturn("/nifi-api");
+        when(request.getPathInfo()).thenReturn("/controller/provenance/events/0/content/input");
+
+        final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
+        final List<String> chain = result.getChain();
+        assertEquals(1, chain.size());
+        assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
+
+        verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
+        verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
+    }
+
+    @Test
+    public void testProvenanceOutputContentDownload() throws Exception {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
+        when(request.getContextPath()).thenReturn("/nifi-api");
+        when(request.getPathInfo()).thenReturn("/controller/provenance/events/0/content/output");
+
+        final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
+        final List<String> chain = result.getChain();
+        assertEquals(1, chain.size());
+        assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
+
+        verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
+        verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
+    }
+
+    @Test
+    public void testFlowFileContentDownload() throws Exception {
+        final String uuid = UUID.randomUUID().toString();
+
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
+        when(request.getContextPath()).thenReturn("/nifi-api");
+        when(request.getPathInfo()).thenReturn(String.format("/controller/process-groups/root/connections/%s/flowfiles/%s/content", uuid, uuid));
+
+        final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
+        final List<String> chain = result.getChain();
+        assertEquals(1, chain.size());
+        assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
+
+        verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
+        verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
+    }
+
+    @Test
+    public void testTemplateDownload() throws Exception {
+        final String uuid = UUID.randomUUID().toString();
+
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.isSecure()).thenReturn(true);
+        when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
+        when(request.getContextPath()).thenReturn("/nifi-api");
+        when(request.getPathInfo()).thenReturn(String.format("/controller/templates/%s", uuid));
+
+        final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
+        final List<String> chain = result.getChain();
+        assertEquals(1, chain.size());
+        assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
+
+        verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
+        verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpServiceTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpServiceTest.java
new file mode 100644
index 0000000..56fc9b1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.otp;
+
+import org.apache.nifi.web.security.token.OtpAuthenticationToken;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class OtpServiceTest {
+
+    private final static String USER_1 = "user-identity-1";
+
+    private OtpService otpService;
+
+    @Before
+    public void setUp() throws Exception {
+        otpService = new OtpService();
+    }
+
+    @Test
+    public void testGetAuthenticationForValidDownloadToken() throws Exception {
+        final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
+        final String downloadToken = otpService.generateDownloadToken(authenticationToken);
+        final String authenticatedUser = otpService.getAuthenticationFromDownloadToken(downloadToken);
+
+        assertNotNull(authenticatedUser);
+        assertEquals(USER_1, authenticatedUser);
+
+        try {
+            // ensure the token is no longer valid
+            otpService.getAuthenticationFromDownloadToken(downloadToken);
+            fail();
+        } catch (final OtpAuthenticationException oae) {}
+    }
+
+    @Test
+    public void testGetAuthenticationForValidUiExtensionToken() throws Exception {
+        final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
+        final String uiExtensionToken = otpService.generateUiExtensionToken(authenticationToken);
+        final String authenticatedUser = otpService.getAuthenticationFromUiExtensionToken(uiExtensionToken);
+
+        assertNotNull(authenticatedUser);
+        assertEquals(USER_1, authenticatedUser);
+
+        try {
+            // ensure the token is no longer valid
+            otpService.getAuthenticationFromUiExtensionToken(uiExtensionToken);
+            fail();
+        } catch (final OtpAuthenticationException oae) {}
+    }
+
+    @Test(expected = OtpAuthenticationException.class)
+    public void testGetNonExistentDownloadToken() throws Exception {
+        otpService.getAuthenticationFromDownloadToken("Not a real download token");
+    }
+
+    @Test(expected = OtpAuthenticationException.class)
+    public void testGetNonExistentUiExtensionToken() throws Exception {
+        otpService.getAuthenticationFromUiExtensionToken("Not a real ui extension token");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMaxDownloadTokenLimit() throws Exception {
+        // ensure we'll try to loop past the limit
+        for (int i = 1; i < OtpService.MAX_CACHE_SOFT_LIMIT + 10; i++) {
+            try {
+                final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken("user-identity-" + i);
+                otpService.generateDownloadToken(authenticationToken);
+            } catch (final IllegalStateException iae) {
+                // ensure we failed when we've past the limit
+                assertEquals(OtpService.MAX_CACHE_SOFT_LIMIT + 1, i);
+                throw iae;
+            }
+        }
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMaxUiExtensionTokenLimit() throws Exception {
+        // ensure we'll try to loop past the limit
+        for (int i = 1; i < OtpService.MAX_CACHE_SOFT_LIMIT + 10; i++) {
+            try {
+                final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken("user-identity-" + i);
+                otpService.generateUiExtensionToken(authenticationToken);
+            } catch (final IllegalStateException iae) {
+                // ensure we failed when we've past the limit
+                assertEquals(OtpService.MAX_CACHE_SOFT_LIMIT + 1, i);
+                throw iae;
+            }
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullTimeUnits() throws Exception {
+        new OtpService(0, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNegativeExpiration() throws Exception {
+        new OtpService(-1, TimeUnit.MINUTES);
+    }
+
+    @Test(expected = OtpAuthenticationException.class)
+    public void testUiExtensionTokenExpiration() throws Exception {
+        final OtpService otpServiceWithTightExpiration = new OtpService(2, TimeUnit.SECONDS);
+
+        final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
+        final String downloadToken = otpServiceWithTightExpiration.generateUiExtensionToken(authenticationToken);
+
+        // sleep for 4 seconds which should sufficiently expire the valid token
+        Thread.sleep(4 * 1000);
+
+        // attempt to get the token now that its expired
+        otpServiceWithTightExpiration.getAuthenticationFromUiExtensionToken(downloadToken);
+    }
+
+    @Test(expected = OtpAuthenticationException.class)
+    public void testDownloadTokenExpiration() throws Exception {
+        final OtpService otpServiceWithTightExpiration = new OtpService(2, TimeUnit.SECONDS);
+
+        final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
+        final String downloadToken = otpServiceWithTightExpiration.generateDownloadToken(authenticationToken);
+
+        // sleep for 4 seconds which should sufficiently expire the valid token
+        Thread.sleep(4 * 1000);
+
+        // attempt to get the token now that its expired
+        otpServiceWithTightExpiration.getAuthenticationFromDownloadToken(downloadToken);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
index 3f432c2..cd2f722 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
@@ -280,6 +280,7 @@
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-shell.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-snippet.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-canvas-toolbox.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-custom-ui.js</include>
@@ -336,6 +337,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/history/nf-history.js</include>
                                                 <include>${staging.dir}/js/nf/history/nf-history-table.js</include>
                                                 <include>${staging.dir}/js/nf/history/nf-history-model.js</include>
@@ -350,6 +352,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/provenance/nf-provenance.js</include>
                                                 <include>${staging.dir}/js/nf/provenance/nf-provenance-table.js</include>
                                             </includes>
@@ -363,6 +366,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/nf-processor-details.js</include>
                                                 <include>${staging.dir}/js/nf/nf-connection-details.js</include>
                                                 <include>${staging.dir}/js/nf/summary/nf-summary.js</include>
@@ -379,6 +383,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/counters/nf-counters.js</include>
                                                 <include>${staging.dir}/js/nf/counters/nf-counters-table.js</include>
                                             </includes>
@@ -392,6 +397,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/templates/nf-templates.js</include>
                                                 <include>${staging.dir}/js/nf/templates/nf-templates-table.js</include>
                                             </includes>
@@ -405,6 +411,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/cluster/nf-cluster.js</include>
                                                 <include>${staging.dir}/js/nf/cluster/nf-cluster-table.js</include>
                                             </includes>
@@ -418,6 +425,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/users/nf-users.js</include>
                                                 <include>${staging.dir}/js/nf/users/nf-users-table.js</include>
                                             </includes>
@@ -431,6 +439,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/bulletin-board/nf-bulletin-board.js</include>
                                             </includes>
                                         </aggregation>
@@ -443,6 +452,7 @@
                                                 <include>${staging.dir}/js/nf/nf-universal-capture.js</include>
                                                 <include>${staging.dir}/js/nf/nf-dialog.js</include>
                                                 <include>${staging.dir}/js/nf/nf-storage.js</include>
+                                                <include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
                                                 <include>${staging.dir}/js/nf/login/nf-login.js</include>
                                             </includes>
                                         </aggregation>
@@ -622,6 +632,10 @@
                                 js/nf/nf-namespace.js.gz,
                                 js/nf/nf-universal-capture.js,
                                 js/nf/nf-universal-capture.js.gz,
+                                js/nf/nf-storage.js,
+                                js/nf/nf-storage.js.gz,
+                                js/nf/nf-ajax-setup.js,
+                                js/nf/nf-ajax-setup.js.gz,
                                 js/nf/nf-status-history.js,
                                 js/nf/nf-status-history.js.gz,
                                 js/nf/canvas/nf-canvas-all.js,

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties
index 995b0a8..7fe8584 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties
@@ -18,6 +18,7 @@ nf.bulletin.board.script.tags=<script type="text/javascript" src="js/nf/nf-names
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/bulletin-board/nf-bulletin-board.js?${project.version}"></script>
 nf.bulletin.board.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
 <link rel="stylesheet" href="css/main.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
index e626859..5443907 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
@@ -21,6 +21,7 @@ nf.canvas.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js?
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-shell.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-snippet.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-queue-listing.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-component-state.js?${project.version}"></script>\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties
index 183ec6a..0fb872c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties
@@ -18,6 +18,7 @@ nf.cluster.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/cluster/nf-cluster.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/cluster/nf-cluster-table.js?${project.version}"></script>
 nf.cluster.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties
index cfa7f36..4c947b4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties
@@ -18,6 +18,7 @@ nf.counters.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.j
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/counters/nf-counters.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/counters/nf-counters-table.js?${project.version}"></script>
 nf.counters.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties
index 0850127..6cee206 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties
@@ -18,6 +18,7 @@ nf.history.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/history/nf-history.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/history/nf-history-table.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/history/nf-history-model.js?${project.version}"></script>

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties
index 7a65ac6..7a1678d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties
@@ -17,6 +17,7 @@ nf.login.script.tags=<script type="text/javascript" src="js/nf/nf-common.js?${pr
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/login/nf-login.js?${project.version}"></script>
 nf.login.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
 <link rel="stylesheet" href="css/main.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties
index 8d825cb..dee70b8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties
@@ -18,6 +18,7 @@ nf.provenance.script.tags=<script type="text/javascript" src="js/nf/nf-namespace
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/provenance/nf-provenance.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/provenance/nf-provenance-table.js?${project.version}"></script>
 nf.provenance.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
index b7a26ba..752ca68 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
@@ -18,6 +18,7 @@ nf.summary.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-processor-details.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-connection-details.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/summary/nf-summary.js?${project.version}"></script>\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties
index a9299fc..c0cee4c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties
@@ -18,6 +18,7 @@ nf.templates.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/templates/nf-templates.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/templates/nf-templates-table.js?${project.version}"></script>
 nf.templates.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties
index 160cb49..0b37363 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties
@@ -18,6 +18,7 @@ nf.users.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js?$
 <script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/users/nf-users.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/users/nf-users-table.js?${project.version}"></script>
 nf.users.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 15eda10..0a9cf28 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -125,7 +125,7 @@ nf.CanvasUtils = (function () {
                 }
             }
         },
-        
+
         /**
          * Calculates the point on the specified bounding box that is closest to the
          * specified point.

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-custom-ui.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-custom-ui.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-custom-ui.js
index 365831a..469dcba 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-custom-ui.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-custom-ui.js
@@ -26,22 +26,38 @@ nf.CustomUi = {
      * @argument {boolean} editable         Whether the custom ui should support editing
      */
     showCustomUi: function (id, uri, editable) {
+        return $.Deferred(function (deferred) {
+            nf.Common.getAccessToken('../nifi-api/access/ui-extension-token').done(function (uiExtensionToken) {
+                // record the processor id
+                $('#shell-close-button');
 
-        // record the processor id
-        $('#shell-close-button');
+                var revision = nf.Client.getRevision();
 
-        var revision = nf.Client.getRevision();
+                // build the customer ui params
+                var customUiParams = {
+                    'id': id,
+                    'processorId': id,                  // deprecated
+                    'revision': revision.version,
+                    'clientId': revision.clientId,
+                    'editable': editable
+                };
 
-        // build the customer ui params
-        var customUiParams = {
-            'id': id,
-            'processorId': id,                  // deprecated
-            'revision': revision.version,
-            'clientId': revision.clientId,
-            'editable': editable
-        };
+                // conditionally include the ui extension token
+                if (!nf.Common.isBlank(uiExtensionToken)) {
+                    customUiParams['access_token'] = uiExtensionToken;
+                }
 
-        // show the shell
-        return nf.Shell.showPage('..' + uri + '?' + $.param(customUiParams), false);
+                // show the shell
+                nf.Shell.showPage('..' + uri + '?' + $.param(customUiParams), false).done(function () {
+                    deferred.resolve();
+                });
+            }).fail(function () {
+                nf.Dialog.showOkDialog({
+                    dialogContent: 'Unable to generate access token for accessing the advanced configuration dialog.',
+                    overlayBackground: false
+                });
+                deferred.resolve();
+            });
+        }).promise();
     }
 };
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-queue-listing.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-queue-listing.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-queue-listing.js
index ef88c70..76d368e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-queue-listing.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-queue-listing.js
@@ -23,6 +23,16 @@
 nf.QueueListing = (function () {
 
     /**
+     * Configuration object used to hold a number of configuration items.
+     */
+    var config = {
+        urls: {
+            uiExtensionToken: '../nifi-api/access/ui-extension-token',
+            downloadToken: '../nifi-api/access/download-token'
+        }
+    };
+
+    /**
      * Initializes the listing request status dialog.
      */
     var initializeListingRequestStatusDialog = function () {
@@ -57,15 +67,33 @@ nf.QueueListing = (function () {
     var downloadContent = function () {
         var dataUri = $('#flowfile-uri').text() + '/content';
 
-        // conditionally include the cluster node id
-        var clusterNodeId = $('#flowfile-cluster-node-id').text();
-        if (!nf.Common.isBlank(clusterNodeId)) {
-            window.open(dataUri + '?' + $.param({
-                    'clusterNodeId': clusterNodeId
-                }));
-        } else {
-            window.open(dataUri);
-        }
+        // perform the request once we've received a token
+        nf.Common.getAccessToken(config.urls.downloadToken).done(function (downloadToken) {
+            var parameters = {};
+
+            // conditionally include the ui extension token
+            if (!nf.Common.isBlank(downloadToken)) {
+                parameters['access_token'] = downloadToken;
+            }
+
+            // conditionally include the cluster node id
+            var clusterNodeId = $('#flowfile-cluster-node-id').text();
+            if (!nf.Common.isBlank(clusterNodeId)) {
+                parameters['clusterNodeId'] = clusterNodeId;
+            }
+
+            // open the url
+            if ($.isEmptyObject(parameters)) {
+                window.open(dataUri);
+            } else {
+                window.open(dataUri + '?' + $.param(parameters));
+            }
+        }).fail(function () {
+            nf.Dialog.showOkDialog({
+                dialogContent: 'Unable to generate access token for downloading content.',
+                overlayBackground: false
+            });
+        });
     };
 
     /**
@@ -74,32 +102,80 @@ nf.QueueListing = (function () {
     var viewContent = function () {
         var dataUri = $('#flowfile-uri').text() + '/content';
 
-        // conditionally include the cluster node id
-        var clusterNodeId = $('#flowfile-cluster-node-id').text();
-        if (!nf.Common.isBlank(clusterNodeId)) {
-            var parameters = {
-                'clusterNodeId': clusterNodeId
-            };
+        // generate tokens as necessary
+        var getAccessTokens = $.Deferred(function (deferred) {
+            if (nf.Storage.hasItem('jwt')) {
+                // generate a token for the ui extension and another for the callback
+                var uiExtensionToken = $.ajax({
+                    type: 'POST',
+                    url: config.urls.uiExtensionToken
+                });
+                var downloadToken = $.ajax({
+                    type: 'POST',
+                    url: config.urls.downloadToken
+                });
 
-            dataUri = dataUri + '?' + $.param(parameters);
-        }
+                // wait for each token
+                $.when(uiExtensionToken, downloadToken).done(function (uiExtensionTokenResult, downloadTokenResult) {
+                    var uiExtensionToken = uiExtensionTokenResult[0];
+                    var downloadToken = downloadTokenResult[0];
+                    deferred.resolve(uiExtensionToken, downloadToken);
+                }).fail(function () {
+                    nf.Dialog.showOkDialog({
+                        dialogContent: 'Unable to generate access token for viewing content.',
+                        overlayBackground: false
+                    });
+                    deferred.reject();
+                });
+            } else {
+                deferred.resolve('', '');
+            }
+        }).promise();
 
-        // open the content viewer
-        var contentViewerUrl = $('#nifi-content-viewer-url').text();
+        // perform the request after we've received the tokens
+        getAccessTokens.done(function (uiExtensionToken, downloadToken) {
+            var dataUriParameters = {};
 
-        // if there's already a query string don't add another ?... this assumes valid
-        // input meaning that if the url has already included a ? it also contains at
-        // least one query parameter
-        if (contentViewerUrl.indexOf('?') === -1) {
-            contentViewerUrl += '?';
-        } else {
-            contentViewerUrl += '&';
-        }
+            // conditionally include the cluster node id
+            var clusterNodeId = $('#flowfile-cluster-node-id').text();
+            if (!nf.Common.isBlank(clusterNodeId)) {
+                dataUriParameters['clusterNodeId'] = clusterNodeId;
+            }
+
+            // include the download token if applicable
+            if (!nf.Common.isBlank(downloadToken)) {
+                dataUriParameters['access_token'] = downloadToken;
+            }
+
+            // include parameters if necessary
+            if ($.isEmptyObject(dataUriParameters) === false) {
+                dataUri = dataUri + '?' + $.param(dataUriParameters);
+            }
+
+            // open the content viewer
+            var contentViewerUrl = $('#nifi-content-viewer-url').text();
+
+            // if there's already a query string don't add another ?... this assumes valid
+            // input meaning that if the url has already included a ? it also contains at
+            // least one query parameter
+            if (contentViewerUrl.indexOf('?') === -1) {
+                contentViewerUrl += '?';
+            } else {
+                contentViewerUrl += '&';
+            }
 
-        // open the content viewer
-        window.open(contentViewerUrl + $.param({
+            var contentViewerParameters = {
                 'ref': dataUri
-            }));
+            };
+
+            // include the download token if applicable
+            if (!nf.Common.isBlank(uiExtensionToken)) {
+                contentViewerParameters['access_token'] = uiExtensionToken;
+            }
+
+            // open the content viewer
+            window.open(contentViewerUrl + $.param(contentViewerParameters));
+        });
     };
 
     /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-ajax-setup.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-ajax-setup.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-ajax-setup.js
new file mode 100644
index 0000000..a041207
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-ajax-setup.js
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Performs ajax setup for use within NiFi.
+ */
+$(document).ready(function () {
+    // include jwt when possible
+    $.ajaxSetup({
+        'beforeSend': function(xhr) {
+            var hadToken = nf.Storage.hasItem('jwt');
+
+            // get the token to include in all requests
+            var token = nf.Storage.getItem('jwt');
+            if (token !== null) {
+                xhr.setRequestHeader('Authorization', 'Bearer ' + token);
+            } else {
+                // if the current user was logged in with a token and the token just expired, cancel the request
+                if (hadToken === true) {
+                    return false;
+                }
+            }
+        }
+    });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index bf88a09..7bf401e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -51,24 +51,6 @@ $(document).ready(function () {
         $('div.loading-container').removeClass('ajax-loading');
     });
     
-    // include jwt when possible
-    $.ajaxSetup({
-        'beforeSend': function(xhr) {
-            var hadToken = nf.Storage.hasItem('jwt');
-            
-            // get the token to include in all requests
-            var token = nf.Storage.getItem('jwt');
-            if (token !== null) {
-                xhr.setRequestHeader('Authorization', 'Bearer ' + token);
-            } else {
-                // if the current user was logged in with a token and the token just expired, reload
-                if (hadToken === true) {
-                    return false;
-                }
-            }
-        }
-    });
-
     // initialize the tooltips
     $('img.setting-icon').qtip(nf.Common.config.tooltipConfig);
     
@@ -768,6 +750,29 @@ nf.Common = (function () {
         },
 
         /**
+         * Gets an access token from the specified url.
+         *
+         * @param accessTokenUrl    The access token
+         * @returns the access token as a deferred
+         */
+        getAccessToken: function (accessTokenUrl) {
+            return $.Deferred(function (deferred) {
+                if (nf.Storage.hasItem('jwt')) {
+                    $.ajax({
+                        type: 'POST',
+                        url: accessTokenUrl
+                    }).done(function (token) {
+                        deferred.resolve(token);
+                    }).fail(function () {
+                        deferred.reject();
+                    })
+                } else {
+                    deferred.resolve('');
+                }
+            }).promise();
+        },
+
+        /**
          * Constants for time duration formatting.
          */
         MILLIS_PER_DAY: 86400000,

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
index 7b0c3f6..efb90fe 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
@@ -20,7 +20,20 @@
 nf.Storage = (function () {
 
     // Store items for two days before being eligible for removal.
-    var TWO_DAYS = nf.Common.MILLIS_PER_DAY * 2;
+    var MILLIS_PER_DAY = 86400000;
+    var TWO_DAYS = MILLIS_PER_DAY * 2;
+
+    var isUndefined = function (obj) {
+        return typeof obj === 'undefined';
+    };
+
+    var isNull = function (obj) {
+        return obj === null;
+    };
+
+    var isDefinedAndNotNull = function (obj) {
+        return !isUndefined(obj) && !isNull(obj);
+    };
 
     /**
      * Checks the expiration for the specified entry.
@@ -29,7 +42,7 @@ nf.Storage = (function () {
      * @returns {boolean}
      */
     var checkExpiration = function (entry) {
-        if (nf.Common.isDefinedAndNotNull(entry.expires)) {
+        if (isDefinedAndNotNull(entry.expires)) {
             // get the expiration
             var expires = new Date(entry.expires);
             var now = new Date();
@@ -52,7 +65,7 @@ nf.Storage = (function () {
             var entry = JSON.parse(localStorage.getItem(key));
 
             // ensure the entry and item are present
-            if (nf.Common.isDefinedAndNotNull(entry)) {
+            if (isDefinedAndNotNull(entry)) {
                 return entry;
             } else {
                 return null;
@@ -89,7 +102,7 @@ nf.Storage = (function () {
          */
         setItem: function (key, item, expires) {
             // calculate the expiration
-            expires = nf.Common.isDefinedAndNotNull(expires) ? expires : new Date().valueOf() + TWO_DAYS;
+            expires = isDefinedAndNotNull(expires) ? expires : new Date().valueOf() + TWO_DAYS;
 
             // create the entry
             var entry = {
@@ -132,7 +145,7 @@ nf.Storage = (function () {
             }
 
             // if the entry has the specified field return its value
-            if (nf.Common.isDefinedAndNotNull(entry['item'])) {
+            if (isDefinedAndNotNull(entry['item'])) {
                 return entry['item'];
             } else {
                 return null;
@@ -153,7 +166,7 @@ nf.Storage = (function () {
             }
 
             // if the entry has the specified field return its value
-            if (nf.Common.isDefinedAndNotNull(entry['expires'])) {
+            if (isDefinedAndNotNull(entry['expires'])) {
                 return entry['expires'];
             } else {
                 return null;

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
index cdad348..a977e08 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
@@ -37,7 +37,9 @@ nf.ProvenanceTable = (function () {
             provenance: '../nifi-api/controller/provenance',
             cluster: '../nifi-api/cluster',
             d3Script: 'js/d3/d3.min.js',
-            lineageScript: 'js/nf/provenance/nf-provenance-lineage.js'
+            lineageScript: 'js/nf/provenance/nf-provenance-lineage.js',
+            uiExtensionToken: '../nifi-api/access/ui-extension-token',
+            downloadToken: '../nifi-api/access/download-token'
         }
     };
 
@@ -78,17 +80,35 @@ nf.ProvenanceTable = (function () {
         var eventId = $('#provenance-event-id').text();
 
         // build the url
-        var url = config.urls.provenance + '/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
-
-        // conditionally include the cluster node id
-        var clusterNodeId = $('#provenance-event-cluster-node-id').text();
-        if (!nf.Common.isBlank(clusterNodeId)) {
-            window.open(url + '?' + $.param({
-                'clusterNodeId': clusterNodeId
-            }));
-        } else {
-            window.open(url);
-        }
+        var dataUri = config.urls.provenance + '/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
+
+        // perform the request once we've received a token
+        nf.Common.getAccessToken(config.urls.downloadToken).done(function (downloadToken) {
+            var parameters = {};
+
+            // conditionally include the ui extension token
+            if (!nf.Common.isBlank(downloadToken)) {
+                parameters['access_token'] = downloadToken;
+            }
+
+            // conditionally include the cluster node id
+            var clusterNodeId = $('#provenance-event-cluster-node-id').text();
+            if (!nf.Common.isBlank(clusterNodeId)) {
+                parameters['clusterNodeId'] = clusterNodeId;
+            }
+
+            // open the url
+            if ($.isEmptyObject(parameters)) {
+                window.open(dataUri);
+            } else {
+                window.open(dataUri + '?' + $.param(parameters));
+            }
+        }).fail(function () {
+            nf.Dialog.showOkDialog({
+                dialogContent: 'Unable to generate access token for downloading content.',
+                overlayBackground: false
+            });
+        });
     };
 
     /**
@@ -103,32 +123,80 @@ nf.ProvenanceTable = (function () {
         // build the uri to the data
         var dataUri = controllerUri + '/provenance/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
 
-        // conditionally include the cluster node id
-        var clusterNodeId = $('#provenance-event-cluster-node-id').text();
-        if (!nf.Common.isBlank(clusterNodeId)) {
-            var parameters = {
-                'clusterNodeId': clusterNodeId
-            };
+        // generate tokens as necessary
+        var getAccessTokens = $.Deferred(function (deferred) {
+            if (nf.Storage.hasItem('jwt')) {
+                // generate a token for the ui extension and another for the callback
+                var uiExtensionToken = $.ajax({
+                    type: 'POST',
+                    url: config.urls.uiExtensionToken
+                });
+                var downloadToken = $.ajax({
+                    type: 'POST',
+                    url: config.urls.downloadToken
+                });
 
-            dataUri = dataUri + '?' + $.param(parameters);
-        }
+                // wait for each token
+                $.when(uiExtensionToken, downloadToken).done(function (uiExtensionTokenResult, downloadTokenResult) {
+                    var uiExtensionToken = uiExtensionTokenResult[0];
+                    var downloadToken = downloadTokenResult[0];
+                    deferred.resolve(uiExtensionToken, downloadToken);
+                }).fail(function () {
+                    nf.Dialog.showOkDialog({
+                        dialogContent: 'Unable to generate access token for viewing content.',
+                        overlayBackground: false
+                    });
+                    deferred.reject();
+                });
+            } else {
+                deferred.resolve('', '');
+            }
+        }).promise();
 
-        // open the content viewer
-        var contentViewerUrl = $('#nifi-content-viewer-url').text();
+        // perform the request after we've received the tokens
+        getAccessTokens.done(function (uiExtensionToken, downloadToken) {
+            var dataUriParameters = {};
 
-        // if there's already a query string don't add another ?... this assumes valid
-        // input meaning that if the url has already included a ? it also contains at
-        // least one query parameter 
-        if (contentViewerUrl.indexOf('?') === -1) {
-            contentViewerUrl += '?';
-        } else {
-            contentViewerUrl += '&';
-        }
+            // conditionally include the cluster node id
+            var clusterNodeId = $('#provenance-event-cluster-node-id').text();
+            if (!nf.Common.isBlank(clusterNodeId)) {
+                dataUriParameters['clusterNodeId'] = clusterNodeId;
+            }
+
+            // include the download token if applicable
+            if (!nf.Common.isBlank(downloadToken)) {
+                dataUriParameters['access_token'] = downloadToken;
+            }
 
-        // open the content viewer
-        window.open(contentViewerUrl + $.param({
-            'ref': dataUri
-        }));
+            // include parameters if necessary
+            if ($.isEmptyObject(dataUriParameters) === false) {
+                dataUri = dataUri + '?' + $.param(dataUriParameters);
+            }
+
+            // open the content viewer
+            var contentViewerUrl = $('#nifi-content-viewer-url').text();
+
+            // if there's already a query string don't add another ?... this assumes valid
+            // input meaning that if the url has already included a ? it also contains at
+            // least one query parameter
+            if (contentViewerUrl.indexOf('?') === -1) {
+                contentViewerUrl += '?';
+            } else {
+                contentViewerUrl += '&';
+            }
+
+            var contentViewerParameters = {
+                'ref': dataUri
+            };
+
+            // include the download token if applicable
+            if (!nf.Common.isBlank(uiExtensionToken)) {
+                contentViewerParameters['access_token'] = uiExtensionToken;
+            }
+
+            // open the content viewer
+            window.open(contentViewerUrl + $.param(contentViewerParameters));
+        });
     };
 
     /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
index 9ec742f..0f9ea8a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
@@ -28,7 +28,8 @@ nf.TemplatesTable = (function () {
             filterList: 'templates-filter-list'
         },
         urls: {
-            templates: '../nifi-api/controller/templates'
+            templates: '../nifi-api/controller/templates',
+            downloadToken: '../nifi-api/access/download-token'
         }
     };
 
@@ -150,6 +151,34 @@ nf.TemplatesTable = (function () {
         return item[args.property].search(filterExp) >= 0;
     };
 
+    /**
+     * Downloads the specified template.
+     *
+     * @param {object} template     The template
+     */
+    var downloadTemplate = function (template) {
+        nf.Common.getAccessToken(config.urls.downloadToken).done(function (downloadToken) {
+            var parameters = {};
+
+            // conditionally include the download token
+            if (!nf.Common.isBlank(downloadToken)) {
+                parameters['access_token'] = downloadToken;
+            }
+
+            // open the url
+            if ($.isEmptyObject(parameters)) {
+                window.open(config.urls.templates + '/' + encodeURIComponent(template.id));
+            } else {
+                window.open(config.urls.templates + '/' + encodeURIComponent(template.id) + '?' + $.param(parameters));
+            }
+        }).fail(function () {
+            nf.Dialog.showOkDialog({
+                dialogContent: 'Unable to generate access token for downloading content.',
+                overlayBackground: false
+            });
+        });
+    };
+
     return {
         /**
          * Initializes the templates list.
@@ -262,7 +291,7 @@ nf.TemplatesTable = (function () {
                 // determine the desired action
                 if (templatesGrid.getColumns()[args.cell].id === 'actions') {
                     if (target.hasClass('export-template')) {
-                        window.open(config.urls.templates + '/' + encodeURIComponent(item.id));
+                        downloadTemplate(item);
                     } else if (target.hasClass('prompt-to-delete-template')) {
                         promptToDeleteTemplate(item);
                     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/java/org/apache/nifi/update/attributes/api/RuleResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/java/org/apache/nifi/update/attributes/api/RuleResource.java b/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/java/org/apache/nifi/update/attributes/api/RuleResource.java
index f68227a..b211b72 100644
--- a/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/java/org/apache/nifi/update/attributes/api/RuleResource.java
+++ b/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/java/org/apache/nifi/update/attributes/api/RuleResource.java
@@ -148,7 +148,7 @@ public class RuleResource {
             try {
                 criteria.reorder(requestEntity.getRuleOrder());
             } catch (final IllegalArgumentException iae) {
-                throw new WebApplicationException(badRequest(iae.getMessage()));
+                throw new WebApplicationException(iae, badRequest(iae.getMessage()));
             }
         }
 
@@ -157,7 +157,7 @@ public class RuleResource {
             try {
                 criteria.setFlowFilePolicy(FlowFilePolicy.valueOf(requestEntity.getFlowFilePolicy()));
             } catch (final IllegalArgumentException iae) {
-                throw new WebApplicationException(badRequest("The specified matching strategy is unknown: " + requestEntity.getFlowFilePolicy()));
+                throw new WebApplicationException(iae, badRequest("The specified matching strategy is unknown: " + requestEntity.getFlowFilePolicy()));
             }
         }
 
@@ -227,7 +227,7 @@ public class RuleResource {
             rule = factory.createRule(ruleDto);
             rule.setId(uuid);
         } catch (final IllegalArgumentException iae) {
-            throw new WebApplicationException(badRequest(iae.getMessage()));
+            throw new WebApplicationException(iae, badRequest(iae.getMessage()));
         }
 
         // add the rule
@@ -268,7 +268,7 @@ public class RuleResource {
             condition = factory.createCondition(requestEntity.getCondition());
             condition.setId(uuid);
         } catch (final IllegalArgumentException iae) {
-            throw new WebApplicationException(badRequest(iae.getMessage()));
+            throw new WebApplicationException(iae, badRequest(iae.getMessage()));
         }
 
         // build the response
@@ -302,7 +302,7 @@ public class RuleResource {
             action = factory.createAction(requestEntity.getAction());
             action.setId(uuid);
         } catch (final IllegalArgumentException iae) {
-            throw new WebApplicationException(badRequest(iae.getMessage()));
+            throw new WebApplicationException(iae, badRequest(iae.getMessage()));
         }
 
         // build the response
@@ -522,7 +522,7 @@ public class RuleResource {
             rule.setConditions(conditions);
             rule.setActions(actions);
         } catch (final IllegalArgumentException iae) {
-            throw new WebApplicationException(badRequest(iae.getMessage()));
+            throw new WebApplicationException(iae, badRequest(iae.getMessage()));
         }
 
         // add the new rule if application
@@ -598,11 +598,11 @@ public class RuleResource {
             // load the processor configuration
             processorDetails = configurationContext.getComponentDetails(requestContext);
         } catch (final InvalidRevisionException ire) {
-            throw new WebApplicationException(invalidRevision(ire.getMessage()));
+            throw new WebApplicationException(ire, invalidRevision(ire.getMessage()));
         } catch (final Exception e) {
             final String message = String.format("Unable to get UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
             logger.error(message, e);
-            throw new WebApplicationException(error(message));
+            throw new WebApplicationException(e, error(message));
         }
 
         Criteria criteria = null;
@@ -612,7 +612,7 @@ public class RuleResource {
             } catch (final IllegalArgumentException iae) {
                 final String message = String.format("Unable to deserialize existing rules for UpdateAttribute[id=%s]. Deserialization error: %s", requestContext.getId(), iae);
                 logger.error(message, iae);
-                throw new WebApplicationException(error(message));
+                throw new WebApplicationException(iae, error(message));
             }
         }
         // ensure the criteria isn't null
@@ -634,11 +634,11 @@ public class RuleResource {
             // save the annotation data
             configurationContext.setAnnotationData(requestContext, annotationData);
         } catch (final InvalidRevisionException ire) {
-            throw new WebApplicationException(invalidRevision(ire.getMessage()));
+            throw new WebApplicationException(ire, invalidRevision(ire.getMessage()));
         } catch (final Exception e) {
             final String message = String.format("Unable to save UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
             logger.error(message, e);
-            throw new WebApplicationException(error(message));
+            throw new WebApplicationException(e, error(message));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/worksheet.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/worksheet.jsp b/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/worksheet.jsp
index a7f93f1..a92c4e0 100644
--- a/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/worksheet.jsp
+++ b/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/worksheet.jsp
@@ -53,6 +53,9 @@
         <script type="text/javascript" src="../nifi/js/jquery/slickgrid/slick.grid.js"></script>
         <script type="text/javascript" src="../nifi/js/codemirror/lib/codemirror-compressed.js"></script>
         <script type="text/javascript" src="../nifi/js/nf/nf-namespace.js"></script>
+        <script type="text/javascript" src="../nifi/js/nf/nf-common.js"></script>
+        <script type="text/javascript" src="../nifi/js/nf/nf-storage.js"></script>
+        <script type="text/javascript" src="../nifi/js/nf/nf-ajax-setup.js"></script>
         <script type="text/javascript" src="../nifi/js/nf/nf-universal-capture.js"></script>
         <script type="text/javascript" src="../nifi/js/jquery/nfeditor/languages/nfel.js"></script>
         <script type="text/javascript" src="../nifi/js/jquery/nfeditor/jquery.nfeditor.js"></script>


[2/2] nifi git commit: NIFI-1497: - Introducing a one time use password service for use in query parameters when accessing UI extensions and downloading resources. - Using one time use tokens when accessing ui extensions and downloading resources. - Ensu

Posted by bb...@apache.org.
NIFI-1497: - Introducing a one time use password service for use in query parameters when accessing UI extensions and downloading resources. - Using one time use tokens when accessing ui extensions and downloading resources. - Ensuring appropriate roles when accessing component details through the web context for custom UIs. - Addressing typo in class name. - Ensuring appropriate roles when accessing content through the content access. - Code clean up. - Refactoring some basic scripts for accessing JWT tokens so UI extensions can reuse common functionality.

Signed-off-by: Bryan Bende <bb...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/a8edab2e
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/a8edab2e
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/a8edab2e

Branch: refs/heads/master
Commit: a8edab2e7955b175c5cbd2a3266fb51fa9712aee
Parents: 86ab442
Author: Matt Gilman <ma...@gmail.com>
Authored: Fri Feb 19 10:50:31 2016 -0500
Committer: Bryan Bende <bb...@apache.org>
Committed: Fri Feb 19 10:54:53 2016 -0500

----------------------------------------------------------------------
 .../web/NiFiWebApiSecurityConfiguration.java    |  35 ++-
 .../nifi/web/StandardNiFiContentAccess.java     |  70 ++++--
 .../StandardNiFiWebConfigurationContext.java    |   4 +
 .../apache/nifi/web/StandardNiFiWebContext.java |   4 +
 .../org/apache/nifi/web/api/AccessResource.java | 122 ++++++++++-
 .../src/main/resources/nifi-web-api-context.xml |   1 +
 .../accesscontrol/AccessTokenEndpointTest.java  |   2 +-
 .../nifi/web/ContentViewerController.java       |  40 +++-
 .../src/main/webapp/WEB-INF/jsp/header.jsp      |  64 +++++-
 .../src/main/webapp/css/main.css                |   8 +
 .../web/security/NiFiAuthenticationFilter.java  |   6 +-
 .../security/NiFiAuthenticationProvider.java    |  10 +-
 .../anonymous/NiFiAnonymousUserFilter.java      |   7 +-
 .../authorization/NiFiAuthorizationService.java |   6 +-
 .../security/jwt/JwtAuthenticationFilter.java   |   6 +-
 .../otp/OtpAuthenticationException.java         |  34 +++
 .../security/otp/OtpAuthenticationFilter.java   |  96 ++++++++
 .../nifi/web/security/otp/OtpService.java       | 219 +++++++++++++++++++
 .../NewAccountAuthorizationRequestToken.java    |   2 +-
 .../token/NiFiAuthorizationRequestToken.java    |  54 +++++
 .../token/NiFiAuthortizationRequestToken.java   |  54 -----
 .../security/token/OtpAuthenticationToken.java  |  56 +++++
 .../security/x509/X509AuthenticationFilter.java |   6 +-
 .../resources/nifi-web-security-context.xml     |   5 +-
 .../NiFiAuthorizationServiceTest.java           |   6 +-
 .../otp/OtpAuthenticationFilterTest.java        | 210 ++++++++++++++++++
 .../nifi/web/security/otp/OtpServiceTest.java   | 149 +++++++++++++
 .../nifi-framework/nifi-web/nifi-web-ui/pom.xml |  14 ++
 .../resources/filters/bulletin-board.properties |   1 +
 .../main/resources/filters/canvas.properties    |   1 +
 .../main/resources/filters/cluster.properties   |   1 +
 .../main/resources/filters/counters.properties  |   1 +
 .../main/resources/filters/history.properties   |   1 +
 .../src/main/resources/filters/login.properties |   1 +
 .../resources/filters/provenance.properties     |   1 +
 .../main/resources/filters/summary.properties   |   1 +
 .../main/resources/filters/templates.properties |   1 +
 .../src/main/resources/filters/users.properties |   1 +
 .../main/webapp/js/nf/canvas/nf-canvas-utils.js |   2 +-
 .../main/webapp/js/nf/canvas/nf-custom-ui.js    |  42 ++--
 .../webapp/js/nf/canvas/nf-queue-listing.js     | 136 +++++++++---
 .../src/main/webapp/js/nf/nf-ajax-setup.js      |  39 ++++
 .../src/main/webapp/js/nf/nf-common.js          |  41 ++--
 .../src/main/webapp/js/nf/nf-storage.js         |  25 ++-
 .../js/nf/provenance/nf-provenance-table.js     | 136 +++++++++---
 .../js/nf/templates/nf-templates-table.js       |  33 ++-
 .../update/attributes/api/RuleResource.java     |  22 +-
 .../src/main/webapp/WEB-INF/jsp/worksheet.jsp   |   3 +
 48 files changed, 1550 insertions(+), 229 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
index 1488aba..64ecc5f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
@@ -24,7 +24,9 @@ import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
 import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
 import org.apache.nifi.web.security.jwt.JwtService;
 import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.otp.OtpAuthenticationFilter;
+import org.apache.nifi.web.security.otp.OtpService;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.apache.nifi.web.security.x509.X509AuthenticationFilter;
 import org.apache.nifi.web.security.x509.X509CertificateExtractor;
 import org.apache.nifi.web.security.x509.X509IdentityProvider;
@@ -54,12 +56,14 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     private UserService userService;
     private AuthenticationUserDetailsService userDetailsService;
     private JwtService jwtService;
+    private OtpService otpService;
     private X509CertificateExtractor certificateExtractor;
     private X509IdentityProvider certificateIdentityProvider;
     private LoginIdentityProvider loginIdentityProvider;
 
     private NodeAuthorizedUserFilter nodeAuthorizedUserFilter;
     private JwtAuthenticationFilter jwtAuthenticationFilter;
+    private OtpAuthenticationFilter otpAuthenticationFilter;
     private X509AuthenticationFilter x509AuthenticationFilter;
     private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
 
@@ -69,9 +73,12 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
 
     @Override
     public void configure(WebSecurity webSecurity) throws Exception {
+        // ignore the access endpoints for obtaining the access config, the access token
+        // granting, and access status for a given user (note: we are not ignoring the
+        // the /access/download-token and /access/ui-extension-token endpoints
         webSecurity
                 .ignoring()
-                    .antMatchers("/access/**");
+                    .antMatchers("/access", "/access/config", "/access/token");
     }
 
     @Override
@@ -95,6 +102,9 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
 
         // jwt
         http.addFilterAfter(jwtFilterBean(), AnonymousAuthenticationFilter.class);
+
+        // otp
+        http.addFilterAfter(otpFilterBean(), AnonymousAuthenticationFilter.class);
     }
 
     @Bean
@@ -135,6 +145,20 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
         return jwtAuthenticationFilter;
     }
 
+    public OtpAuthenticationFilter otpFilterBean() throws Exception {
+        if (otpAuthenticationFilter == null) {
+            otpAuthenticationFilter = new OtpAuthenticationFilter();
+            otpAuthenticationFilter.setProperties(properties);
+            otpAuthenticationFilter.setAuthenticationManager(authenticationManager());
+
+            // only consider the tokens when configured for login
+            if (loginIdentityProvider != null) {
+                otpAuthenticationFilter.setOtpService(otpService);
+            }
+        }
+        return otpAuthenticationFilter;
+    }
+
     @Bean
     public X509AuthenticationFilter x509FilterBean() throws Exception {
         if (x509AuthenticationFilter == null) {
@@ -157,7 +181,7 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     }
 
     @Autowired
-    public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
+    public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
         this.userDetailsService = userDetailsService;
     }
 
@@ -177,6 +201,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     }
 
     @Autowired
+    public void setOtpService(OtpService otpService) {
+        this.otpService = otpService;
+    }
+
+    @Autowired
     public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
         this.loginIdentityProvider = loginIdentityProvider;
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
index f994c52..afaf3ed 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
@@ -17,19 +17,10 @@
 package org.apache.nifi.web;
 
 import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.ClientResponse.Status;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
-import java.io.Serializable;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MultivaluedMap;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.Authority;
 import org.apache.nifi.cluster.manager.NodeResponse;
 import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
 import org.apache.nifi.cluster.manager.impl.WebClusterManager;
@@ -38,13 +29,26 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier;
 import org.apache.nifi.controller.repository.claim.ContentDirection;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.user.NiFiUserDetails;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.apache.nifi.web.util.WebUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedMap;
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  *
  */
@@ -54,17 +58,16 @@ public class StandardNiFiContentAccess implements ContentAccess {
     public static final String CLIENT_ID_PARAM = "clientId";
 
     private static final Pattern FLOWFILE_CONTENT_URI_PATTERN = Pattern
-        .compile("/controller/process-groups/((?:root)|(?:[a-f0-9\\-]{36}))/connections/([a-f0-9\\-]{36})/flowfiles/([a-f0-9\\-]{36})/content");
+        .compile("/controller/process-groups/((?:root)|(?:[a-f0-9\\-]{36}))/connections/([a-f0-9\\-]{36})/flowfiles/([a-f0-9\\-]{36})/content.*");
 
     private static final Pattern PROVENANCE_CONTENT_URI_PATTERN = Pattern
-        .compile("/controller/provenance/events/([0-9]+)/content/((?:input)|(?:output))");
+        .compile("/controller/provenance/events/([0-9]+)/content/((?:input)|(?:output)).*");
 
     private NiFiProperties properties;
     private NiFiServiceFacade serviceFacade;
     private WebClusterManager clusterManager;
 
     @Override
-    @PreAuthorize("hasRole('ROLE_PROVENANCE')")
     public DownloadableContent getContent(final ContentRequestContext request) {
         // if clustered, send request to cluster manager
         if (properties.isClusterManager()) {
@@ -99,6 +102,11 @@ public class StandardNiFiContentAccess implements ContentAccess {
                 }
             }
 
+            // ensure we were able to detect the cluster node id
+            if (request.getClusterNodeId() == null) {
+                throw new IllegalArgumentException("Unable to determine the which node has the content.");
+            }
+
             // get the target node and ensure it exists
             final Node targetNode = clusterManager.getNode(request.getClusterNodeId());
             if (targetNode == null) {
@@ -113,6 +121,16 @@ public class StandardNiFiContentAccess implements ContentAccess {
             final ClientResponse clientResponse = nodeResponse.getClientResponse();
             final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders();
 
+            // ensure an appropriate response
+            if (Status.NOT_FOUND.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) {
+                throw new ResourceNotFoundException(clientResponse.getEntity(String.class));
+            } else if (Status.FORBIDDEN.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()
+                        || Status.UNAUTHORIZED.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) {
+                throw new AccessDeniedException(clientResponse.getEntity(String.class));
+            } else if (Status.OK.getStatusCode() != clientResponse.getStatusInfo().getStatusCode()) {
+                throw new IllegalStateException(clientResponse.getEntity(String.class));
+            }
+
             // get the file name
             final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
             final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");
@@ -140,7 +158,7 @@ public class StandardNiFiContentAccess implements ContentAccess {
                 final String connectionId = flowFileMatcher.group(2);
                 final String flowfileId = flowFileMatcher.group(3);
 
-                return serviceFacade.getContent(groupId, connectionId, flowfileId, dataUri);
+                return getFlowFileContent(groupId, connectionId, flowfileId, dataUri);
             }
 
             // provenance event content
@@ -150,7 +168,7 @@ public class StandardNiFiContentAccess implements ContentAccess {
                     final Long eventId = Long.parseLong(provenanceMatcher.group(1));
                     final ContentDirection direction = ContentDirection.valueOf(provenanceMatcher.group(2).toUpperCase());
 
-                    return serviceFacade.getContent(eventId, dataUri, direction);
+                    return getProvenanceEventContent(eventId, dataUri, direction);
                 } catch (final IllegalArgumentException iae) {
                     throw new IllegalArgumentException("The specified data reference URI is not valid.");
                 }
@@ -161,6 +179,24 @@ public class StandardNiFiContentAccess implements ContentAccess {
         }
     }
 
+    private DownloadableContent getFlowFileContent(final String groupId, final String connectionId, final String flowfileId, final String dataUri) {
+        // ensure the user is authorized as DFM - not checking with @PreAuthorized annotation as aspect not trigger on call within a class
+        if (!NiFiUserUtils.getAuthorities().contains(Authority.ROLE_DFM.toString())) {
+            throw new AccessDeniedException("Access is denied.");
+        }
+
+        return serviceFacade.getContent(groupId, connectionId, flowfileId, dataUri);
+    }
+
+    private DownloadableContent getProvenanceEventContent(final Long eventId, final String dataUri, final ContentDirection direction) {
+        // ensure the user is authorized as Provenance - not checking with @PreAuthorized annotation as aspect not trigger on call within a class
+        if (!NiFiUserUtils.getAuthorities().contains(Authority.ROLE_PROVENANCE.toString())) {
+            throw new AccessDeniedException("Access is denied.");
+        }
+
+        return serviceFacade.getContent(eventId, dataUri, direction);
+    }
+
     public void setProperties(NiFiProperties properties) {
         this.properties = properties;
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
index 9502bf2..cae1175 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
@@ -86,6 +86,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
     private AuditService auditService;
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public ControllerService getControllerService(String serviceIdentifier) {
         return controllerServiceLookup.getControllerService(serviceIdentifier);
     }
@@ -157,6 +158,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
     }
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public String getCurrentUserDn() {
         String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
 
@@ -169,6 +171,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
     }
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public String getCurrentUserName() {
         String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
 
@@ -181,6 +184,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
     }
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) throws ResourceNotFoundException, ClusterRequestException {
         final String id = requestContext.getId();
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
index 7e9e77e..9667ad6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
@@ -81,6 +81,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
     private AuditService auditService;
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public ControllerService getControllerService(String serviceIdentifier) {
         return controllerServiceLookup.getControllerService(serviceIdentifier);
     }
@@ -128,6 +129,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
     }
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public String getCurrentUserDn() {
         String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
 
@@ -140,6 +142,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
     }
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public String getCurrentUserName() {
         String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
 
@@ -152,6 +155,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
     }
 
     @Override
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     public ProcessorInfo getProcessor(final NiFiWebContextConfig config) throws ResourceNotFoundException, ClusterRequestException {
 
         final Revision revision = config.getRevision();

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index 7bf9690..24536fc 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import io.jsonwebtoken.JwtException;
+import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.util.NiFiProperties;
 import com.wordnik.swagger.annotations.Api;
 import com.wordnik.swagger.annotations.ApiOperation;
@@ -60,8 +61,11 @@ import org.apache.nifi.web.security.ProxiedEntitiesUtils;
 import org.apache.nifi.web.security.UntrustedProxyException;
 import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
 import org.apache.nifi.web.security.jwt.JwtService;
+import org.apache.nifi.web.security.otp.OtpService;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
+import org.apache.nifi.web.security.token.OtpAuthenticationToken;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.apache.nifi.web.security.x509.X509CertificateExtractor;
 import org.apache.nifi.web.security.x509.X509IdentityProvider;
 import org.slf4j.Logger;
@@ -92,8 +96,9 @@ public class AccessResource extends ApplicationResource {
     private X509CertificateExtractor certificateExtractor;
     private X509IdentityProvider certificateIdentityProvider;
     private JwtService jwtService;
+    private OtpService otpService;
 
-    private AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService;
+    private AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService;
 
     /**
      * Retrieves the access configuration for this NiFi.
@@ -285,7 +290,107 @@ public class AccessResource extends ApplicationResource {
      * @throws AuthenticationException if the proxy chain is not authorized
      */
     private UserDetails checkAuthorization(final List<String> proxyChain) throws AuthenticationException {
-        return userDetailsService.loadUserDetails(new NiFiAuthortizationRequestToken(proxyChain));
+        return userDetailsService.loadUserDetails(new NiFiAuthorizationRequestToken(proxyChain));
+    }
+
+    /**
+     * Creates a single use access token for downloading FlowFile content.
+     *
+     * @param httpServletRequest    the servlet request
+     * @return  A token (string)
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.TEXT_PLAIN)
+    @Path("/download-token")
+    @ApiOperation(
+        value = "Creates a single use access token for downloading FlowFile content.",
+        notes = "The token returned is a base64 encoded string. It is valid for a single request up to five minutes from being issued. " +
+            "It is used as a query parameter name 'access_token'.",
+        response = String.class
+    )
+    @ApiResponses(
+        value = {
+            @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+            @ApiResponse(code = 409, message = "Unable to create the download token because NiFi is not in the appropriate state. " +
+                "(i.e. may not have any tokens to grant or be configured to support username/password login)"),
+            @ApiResponse(code = 500, message = "Unable to create download token because an unexpected error occurred.")
+        }
+    )
+    public Response createDownloadToken(@Context HttpServletRequest httpServletRequest) {
+        // only support access tokens when communicating over HTTPS
+        if (!httpServletRequest.isSecure()) {
+            throw new IllegalStateException("Download tokens are only issued over HTTPS.");
+        }
+
+        // if not configuration for login, don't consider credentials
+        if (loginIdentityProvider == null) {
+            throw new IllegalStateException("Download tokens not supported by this NiFi.");
+        }
+
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user == null) {
+            throw new AccessDeniedException("Unable to determine user details.");
+        }
+
+        final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(user.getIdentity());
+
+        // generate otp for response
+        final String token = otpService.generateDownloadToken(authenticationToken);
+
+        // build the response
+        final URI uri = URI.create(generateResourceUri("access", "download-token"));
+        return generateCreatedResponse(uri, token).build();
+    }
+
+    /**
+     * Creates a single use access token for accessing a NiFi UI extension.
+     *
+     * @param httpServletRequest    the servlet request
+     * @return  A token (string)
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.TEXT_PLAIN)
+    @Path("/ui-extension-token")
+    @ApiOperation(
+        value = "Creates a single use access token for accessing a NiFi UI extension.",
+        notes = "The token returned is a base64 encoded string. It is valid for a single request up to five minutes from being issued. " +
+            "It is used as a query parameter name 'access_token'.",
+        response = String.class
+    )
+    @ApiResponses(
+        value = {
+            @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+            @ApiResponse(code = 409, message = "Unable to create the download token because NiFi is not in the appropriate state. " +
+                "(i.e. may not have any tokens to grant or be configured to support username/password login)"),
+            @ApiResponse(code = 500, message = "Unable to create download token because an unexpected error occurred.")
+        }
+    )
+    public Response createUiExtensionToken(@Context HttpServletRequest httpServletRequest) {
+        // only support access tokens when communicating over HTTPS
+        if (!httpServletRequest.isSecure()) {
+            throw new IllegalStateException("UI extension access tokens are only issued over HTTPS.");
+        }
+
+        // if not configuration for login, don't consider credentials
+        if (loginIdentityProvider == null) {
+            throw new IllegalStateException("UI extension access tokens not supported by this NiFi.");
+        }
+
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user == null) {
+            throw new AccessDeniedException("Unable to determine user details.");
+        }
+
+        final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(user.getIdentity());
+
+        // generate otp for response
+        final String token = otpService.generateUiExtensionToken(authenticationToken);
+
+        // build the response
+        final URI uri = URI.create(generateResourceUri("access", "ui-extension-token"));
+        return generateCreatedResponse(uri, token).build();
     }
 
     /**
@@ -302,6 +407,9 @@ public class AccessResource extends ApplicationResource {
     @Path("/token")
     @ApiOperation(
             value = "Creates a token for accessing the REST API via username/password",
+            notes = "The token returned is formatted as a JSON Web Token (JWT). The token is base64 encoded and comprised of three parts. The header, " +
+                "the body, and the signature. The expiration of the token is a contained within the body. The token can be used in the Authorization header " +
+                "in the format 'Authorization: Bearer <token>'.",
             response = String.class
     )
     @ApiResponses(
@@ -399,7 +507,7 @@ public class AccessResource extends ApplicationResource {
     private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException {
         if (proxyChain.size() > 1) {
             try {
-                userDetailsService.loadUserDetails(new NiFiAuthortizationRequestToken(proxyChain));
+                userDetailsService.loadUserDetails(new NiFiAuthorizationRequestToken(proxyChain));
             } catch (final UsernameNotFoundException unfe) {
                 // if a username not found exception was thrown, the proxies were authorized and now
                 // we can issue a new token to the end user which they will use to identify themselves
@@ -427,6 +535,10 @@ public class AccessResource extends ApplicationResource {
         this.jwtService = jwtService;
     }
 
+    public void setOtpService(OtpService otpService) {
+        this.otpService = otpService;
+    }
+
     public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
         this.certificateExtractor = certificateExtractor;
     }
@@ -435,7 +547,7 @@ public class AccessResource extends ApplicationResource {
         this.certificateIdentityProvider = certificateIdentityProvider;
     }
 
-    public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
+    public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
         this.userDetailsService = userDetailsService;
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index b38bca5..c9326b2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -259,6 +259,7 @@
         <property name="certificateIdentityProvider" ref="certificateIdentityProvider"/>
         <property name="loginIdentityProvider" ref="loginIdentityProvider"/>
         <property name="jwtService" ref="jwtService"/>
+        <property name="otpService" ref="otpService"/>
         <property name="userDetailsService" ref="userDetailsService"/>
     </bean>
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
index 82fe73a..fe48490 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
@@ -204,7 +204,7 @@ public class AccessTokenEndpointTest {
      * @throws Exception ex
      */
     @Test
-    public void testUnkownUser() throws Exception {
+    public void testUnknownUser() throws Exception {
         String url = BASE_URL + "/access/token";
 
         ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
index a6f6aec..9b053f0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
@@ -21,6 +21,7 @@ import com.ibm.icu.text.CharsetMatch;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URI;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -71,7 +72,19 @@ public class ContentViewerController extends HttpServlet {
         final ServletContext servletContext = request.getServletContext();
         final ContentAccess contentAccess = (ContentAccess) servletContext.getAttribute("nifi-content-access");
 
-        final ContentRequestContext contentRequest = getContentRequest(request);
+        final ContentRequestContext contentRequest;
+        try {
+            contentRequest = getContentRequest(request);
+        } catch (final Exception e) {
+            request.setAttribute("title", "Error");
+            request.setAttribute("messages", "Unable to interpret content request.");
+
+            // forward to the error page
+            final ServletContext viewerContext = servletContext.getContext("/nifi");
+            viewerContext.getRequestDispatcher("/message").forward(request, response);
+            return;
+        }
+
         if (contentRequest.getDataUri() == null) {
             request.setAttribute("title", "Error");
             request.setAttribute("messages", "The data reference must be specified.");
@@ -274,26 +287,41 @@ public class ContentViewerController extends HttpServlet {
      * @return Get the content request context based on the specified request
      */
     private ContentRequestContext getContentRequest(final HttpServletRequest request) {
+        final String ref = request.getParameter("ref");
+        final String clientId = request.getParameter("clientId");
+        final String proxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain");
+
+        final URI refUri = URI.create(ref);
+        final String query = refUri.getQuery();
+        final String[] queryParameters = query.split("&");
+
+        String rawClusterNodeId = null;
+        for (int i = 0; i < queryParameters.length; i++) {
+            if (queryParameters[0].startsWith("clusterNodeId=")) {
+                rawClusterNodeId = StringUtils.substringAfterLast(queryParameters[0], "clusterNodeId=");
+            }
+        }
+        final String clusterNodeId = rawClusterNodeId;
+
         return new ContentRequestContext() {
             @Override
             public String getDataUri() {
-                return request.getParameter("ref");
+                return ref;
             }
 
             @Override
             public String getClusterNodeId() {
-                final String ref = request.getParameter("ref");
-                return StringUtils.substringAfterLast(ref, "clusterNodeId=");
+                return clusterNodeId;
             }
 
             @Override
             public String getClientId() {
-                return request.getParameter("clientId");
+                return clientId;
             }
 
             @Override
             public String getProxiedEntitiesChain() {
-                return request.getHeader("X-ProxiedEntitiesChain");
+                return proxiedEntitiesChain;
             }
         };
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
index 0f6fae3..c000284 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
@@ -25,14 +25,37 @@
         <link href="../nifi/css/message-pane.css" rel="stylesheet" type="text/css" />
         <link href="../nifi/css/message-page.css" rel="stylesheet" type="text/css" />
         <link rel="stylesheet" href="../nifi/js/jquery/combo/jquery.combo.css" type="text/css" />
+        <link rel="stylesheet" href="../nifi/js/jquery/modal/jquery.modal.css" type="text/css" />
         <link rel="stylesheet" href="../nifi/css/reset.css" type="text/css" />
         <script type="text/javascript" src="../nifi/js/jquery/jquery-2.1.1.min.js"></script>
+        <script type="text/javascript" src="../nifi/js/jquery/jquery.center.js"></script>
         <script type="text/javascript" src="../nifi/js/jquery/combo/jquery.combo.js"></script>
+        <script type="text/javascript" src="../nifi/js/jquery/modal/jquery.modal.js"></script>
         <script type="text/javascript" src="../nifi/js/nf/nf-namespace.js"></script>
+        <script type="text/javascript" src="../nifi/js/nf/nf-storage.js"></script>
+        <script type="text/javascript" src="../nifi/js/nf/nf-ajax-setup.js"></script>
         <script type="text/javascript" src="../nifi/js/nf/nf-universal-capture.js"></script>
         <script type="text/javascript">
             var $$ = $.noConflict(true);
             $$(document).ready(function () {
+                // initialize the dialog
+                $$('#content-viewer-message-dialog').modal({
+                    overlayBackground: false,
+                    buttons: [{
+                        buttonText: 'Ok',
+                        handler: {
+                            click: function () {
+                                $$('#content-viewer-message-dialog').modal('hide');
+                            }
+                        }
+                    }],
+                    handler: {
+                        close: function () {
+                            $$('#content-viewer-message').text('');
+                        }
+                    }
+                });
+
                 var url = $$('#requestUrl').text();
                 var ref = $$('#ref').text();
                 
@@ -74,12 +97,40 @@
                             currentLocation = option.value;
                             return;
                         }
-                        
+
                         // if the selection has changesd, reload the page
                         if (currentLocation !== option.value) {
-                            window.location.href = url + '?' + $$.param($$.extend({
-                                mode: option.value
-                            }, params));
+                            // get an access token if necessary
+                            var getAccessToken = $.Deferred(function (deferred) {
+                                if (nf.Storage.hasItem('jwt')) {
+                                    $.ajax({
+                                        type: 'POST',
+                                        url: '../nifi-api/access/ui-extension-token'
+                                    }).done(function (token) {
+                                        deferred.resolve(token);
+                                    }).fail(function () {
+                                        $$('#content-viewer-message').text('Unable to generate a token to view the content.');
+                                        $$('#content-viewer-message-dialog').modal('show');
+                                        deferred.reject();
+                                    })
+                                } else {
+                                    deferred.resolve('');
+                                }
+                            }).promise();
+
+                            // reload as appropriate
+                            getAccessToken.done(function (uiExtensionToken) {
+                                var contentParameter = {
+                                    mode: option.value
+                                };
+
+                                // include the download token if applicable
+                                if (typeof uiExtensionToken !== 'undefined' && uiExtensionToken !== null && $$.trim(uiExtensionToken) !== '') {
+                                    contentParameter['access_token'] = uiExtensionToken;
+                                }
+
+                                window.location.href = url + '?' + $$.param($$.extend(contentParameter, params));
+                            });
                         }
                     }
                 });
@@ -91,6 +142,11 @@
         <span id="clusterNodeId" class="hidden"><%= request.getParameter("clusterNodeId") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getParameter("clusterNodeId")) %></span>
         <span id="mode" class="hidden"><%= request.getParameter("mode") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getParameter("mode")) %></span>
         <span id="requestUrl" class="hidden"><%= org.apache.nifi.util.EscapeUtils.escapeHtml(request.getRequestURL().toString()) %></span>
+        <div id="content-viewer-message-dialog">
+            <div class="dialog-content" style="margin-top: -20px;">
+                <div id="content-viewer-message"></div>
+            </div>
+        </div>
         <div id="view-as-label">View as</div>
         <div id="view-as" class="pointer button-normal"></div>
         <div id="content-filename"><span class="content-label">filename</span><%= request.getAttribute("filename") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getAttribute("filename").toString()) %></div>

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
index fe68b62..6e83c76 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
@@ -19,6 +19,14 @@
     padding: 0;
 }
 
+#content-viewer-message-dialog {
+    z-index: 1302;
+    display: none;
+    height: 225px;
+    width: 375px;
+    font-size: 62.5%
+}
+
 #view-as-label {
     position: absolute;
     top: 72px;

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
index d63f01e..0520ac8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
@@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -82,7 +82,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
     private void authenticate(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
         String dnChain = null;
         try {
-            final NiFiAuthortizationRequestToken authenticated = attemptAuthentication(request);
+            final NiFiAuthorizationRequestToken authenticated = attemptAuthentication(request);
             if (authenticated != null) {
                 dnChain = ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><"));
 
@@ -125,7 +125,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
      * @return The NiFiAutorizationRequestToken used to later authorized the client
      * @throws InvalidAuthenticationException If the request contained an authentication attempt, but could not authenticate
      */
-    public abstract NiFiAuthortizationRequestToken attemptAuthentication(HttpServletRequest request);
+    public abstract NiFiAuthorizationRequestToken attemptAuthentication(HttpServletRequest request);
 
     protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
         if (log.isDebugEnabled()) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
index 0887901..e51a26e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
@@ -18,7 +18,7 @@ package org.apache.nifi.web.security;
 
 import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
 import org.apache.nifi.web.security.token.NewAccountAuthorizationToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.apache.nifi.web.security.token.NiFiAuthorizationToken;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
@@ -32,15 +32,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
  */
 public class NiFiAuthenticationProvider implements AuthenticationProvider {
 
-    private final AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService;
+    private final AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService;
 
-    public NiFiAuthenticationProvider(final AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
+    public NiFiAuthenticationProvider(final AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
         this.userDetailsService = userDetailsService;
     }
 
     @Override
     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-        final NiFiAuthortizationRequestToken request = (NiFiAuthortizationRequestToken) authentication;
+        final NiFiAuthorizationRequestToken request = (NiFiAuthorizationRequestToken) authentication;
 
         try {
             // defer to the nifi user details service to authorize the user
@@ -67,7 +67,7 @@ public class NiFiAuthenticationProvider implements AuthenticationProvider {
 
     @Override
     public boolean supports(Class<?> authentication) {
-        return NiFiAuthortizationRequestToken.class.isAssignableFrom(authentication);
+        return NiFiAuthorizationRequestToken.class.isAssignableFrom(authentication);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
index e67ed2c..05c5fb8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
@@ -58,8 +58,11 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter {
                 user.getAuthorities().addAll(EnumSet.allOf(Authority.class));
             }
 
-            // only create an authentication token if the anonymous user has some authorities
-            if (!user.getAuthorities().isEmpty()) {
+            // only create an authentication token if the anonymous user has some authorities or they are accessing a ui
+            // extension. ui extensions have run this security filter but we shouldn't require authentication/authorization
+            // when accessing static resources like images, js, and css. authentication/authorization is required when
+            // interacting with nifi however and that will be verified in the NiFiWebContext or NiFiWebConfigurationContext
+            if (!user.getAuthorities().isEmpty() || !request.getContextPath().startsWith("/nifi-api")) {
                 NiFiUserDetails userDetails = new NiFiUserDetails(user);
 
                 // get the granted authorities

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
index 75c01bf..dd87cfa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
@@ -30,7 +30,7 @@ import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.UntrustedProxyException;
 import org.apache.nifi.web.security.user.NiFiUserDetails;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.dao.DataAccessException;
@@ -44,7 +44,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
 /**
  * UserDetailsService that will verify user identity and grant user authorities.
  */
-public class NiFiAuthorizationService implements AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> {
+public class NiFiAuthorizationService implements AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> {
 
     private static final Logger logger = LoggerFactory.getLogger(NiFiAuthorizationService.class);
 
@@ -63,7 +63,7 @@ public class NiFiAuthorizationService implements AuthenticationUserDetailsServic
      * @throws org.springframework.dao.DataAccessException ex
      */
     @Override
-    public synchronized UserDetails loadUserDetails(NiFiAuthortizationRequestToken request) throws UsernameNotFoundException, DataAccessException {
+    public synchronized UserDetails loadUserDetails(NiFiAuthorizationRequestToken request) throws UsernameNotFoundException, DataAccessException {
         NiFiUserDetails userDetails = null;
         final List<String> chain = new ArrayList<>(request.getChain());
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
index faf3cde..e13fcea 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
@@ -20,7 +20,7 @@ import io.jsonwebtoken.JwtException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.web.security.NiFiAuthenticationFilter;
 import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.apache.nifi.web.security.user.NewAccountRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
     private JwtService jwtService;
 
     @Override
-    public NiFiAuthortizationRequestToken attemptAuthentication(final HttpServletRequest request) {
+    public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
         // only suppport jwt login when running securely
         if (!request.isSecure()) {
             return null;
@@ -68,7 +68,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
                 if (isNewAccountRequest(request)) {
                     return new NewAccountAuthorizationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
                 } else {
-                    return new NiFiAuthortizationRequestToken(Arrays.asList(jwtPrincipal));
+                    return new NiFiAuthorizationRequestToken(Arrays.asList(jwtPrincipal));
                 }
             } catch (JwtException e) {
                 throw new InvalidAuthenticationException(e.getMessage(), e);

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.java
new file mode 100644
index 0000000..ee2d573
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.otp;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * Thrown if a one time use token fails authentication.
+ */
+public class OtpAuthenticationException extends AuthenticationException {
+
+    public OtpAuthenticationException(String msg) {
+        super(msg);
+    }
+
+    public OtpAuthenticationException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.java
new file mode 100644
index 0000000..9c44298
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.otp;
+
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.apache.nifi.web.security.NiFiAuthenticationFilter;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ */
+public class OtpAuthenticationFilter extends NiFiAuthenticationFilter {
+
+    private static final Logger logger = LoggerFactory.getLogger(OtpAuthenticationFilter.class);
+
+    private static final Pattern PROVENANCE_DOWNLOAD_PATTERN =
+        Pattern.compile("/controller/provenance/events/[0-9]+/content/(?:(?:output)|(?:input))");
+    private static final Pattern QUEUE_DOWNLOAD_PATTERN =
+        Pattern.compile("/controller/process-groups/(?:(?:root)|(?:[a-f0-9\\-]{36}))/connections/[a-f0-9\\-]{36}/flowfiles/[a-f0-9\\-]{36}/content");
+    private static final Pattern TEMPLATE_DOWNLOAD_PATTERN =
+        Pattern.compile("/controller/templates/[a-f0-9\\-]{36}");
+
+    protected static final String ACCESS_TOKEN = "access_token";
+
+    private OtpService otpService;
+
+    @Override
+    public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
+        // only support otp login when running securely
+        if (!request.isSecure()) {
+            return null;
+        }
+
+        // get the accessToken out of the query string
+        final String accessToken = request.getParameter(ACCESS_TOKEN);
+
+        // if there is no authorization header, we don't know the user
+        if (accessToken == null) {
+            return null;
+        } else {
+            if (otpService == null) {
+                throw new InvalidAuthenticationException("NiFi is not configured to support username/password logins.");
+            }
+
+            try {
+                String identity = null;
+                if (request.getContextPath().equals("/nifi-api")) {
+                    if (isDownloadRequest(request.getPathInfo())) {
+                        // handle download requests
+                        identity = otpService.getAuthenticationFromDownloadToken(accessToken);
+                    }
+                } else {
+                    // handle requests to other context paths (other UI extensions)
+                    identity = otpService.getAuthenticationFromUiExtensionToken(accessToken);
+                }
+
+                // the path is a support path for otp tokens
+                if (identity == null) {
+                    return null;
+                }
+
+                return new NiFiAuthorizationRequestToken(Arrays.asList(identity));
+            } catch (final OtpAuthenticationException oae) {
+                throw new InvalidAuthenticationException(oae.getMessage(), oae);
+            }
+        }
+    }
+
+    private boolean isDownloadRequest(final String pathInfo) {
+        return PROVENANCE_DOWNLOAD_PATTERN.matcher(pathInfo).matches() || QUEUE_DOWNLOAD_PATTERN.matcher(pathInfo).matches() || TEMPLATE_DOWNLOAD_PATTERN.matcher(pathInfo).matches();
+    }
+
+    public void setOtpService(OtpService otpService) {
+        this.otpService = otpService;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.java
new file mode 100644
index 0000000..cd5a90c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.java
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.otp;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.nifi.web.security.token.OtpAuthenticationToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * OtpService is a service for generating and verifying one time password tokens.
+ */
+public class OtpService {
+
+    private static final Logger logger = LoggerFactory.getLogger(OtpService.class);
+
+    private static final String HMAC_SHA256 = "HmacSHA256";
+
+    // protected for testing purposes
+    protected static final int MAX_CACHE_SOFT_LIMIT = 100;
+
+    private final Cache<CacheKey, String> downloadTokenCache;
+    private final Cache<CacheKey, String> uiExtensionCache;
+
+    /**
+     * Creates a new OtpService with an expiration of 5 minutes.
+     */
+    public OtpService() {
+        this(5, TimeUnit.MINUTES);
+    }
+
+    /**
+     * Creates a new OtpService.
+     *
+     * @param duration                  The expiration duration
+     * @param units                     The expiration units
+     * @throws NullPointerException     If units is null
+     * @throws IllegalArgumentException If duration is negative
+     */
+    public OtpService(final int duration, final TimeUnit units) {
+        downloadTokenCache = CacheBuilder.newBuilder().expireAfterWrite(duration, units).build();
+        uiExtensionCache = CacheBuilder.newBuilder().expireAfterWrite(duration, units).build();
+    }
+
+    /**
+     * Generates a download token for the specified authentication.
+     *
+     * @param authenticationToken       The authentication
+     * @return                          The one time use download token
+     */
+    public String generateDownloadToken(final OtpAuthenticationToken authenticationToken) {
+        return generateToken(downloadTokenCache.asMap(), authenticationToken);
+    }
+
+    /**
+     * Gets the authenticated identity from the specified one time use download token. This method will not return null.
+     *
+     * @param token                     The one time use download token
+     * @return                          The authenticated identity
+     * @throws OtpAuthenticationException   When the specified token does not correspond to an authenticated identity
+     */
+    public String getAuthenticationFromDownloadToken(final String token) throws OtpAuthenticationException {
+        return getAuthenticationFromToken(downloadTokenCache.asMap(), token);
+    }
+
+    /**
+     * Generates a UI extension token for the specified authentication.
+     *
+     * @param authenticationToken       The authentication
+     * @return                          The one time use UI extension token
+     */
+    public String generateUiExtensionToken(final OtpAuthenticationToken authenticationToken) {
+        return generateToken(uiExtensionCache.asMap(), authenticationToken);
+    }
+
+    /**
+     * Gets the authenticated identity from the specified one time use UI extension token. This method will not return null.
+     *
+     * @param token                     The one time use UI extension token
+     * @return                          The authenticated identity
+     * @throws OtpAuthenticationException   When the specified token does not correspond to an authenticated identity
+     */
+    public String getAuthenticationFromUiExtensionToken(final String token) throws OtpAuthenticationException {
+        return getAuthenticationFromToken(uiExtensionCache.asMap(), token);
+    }
+
+    /**
+     * Generates a token and stores it in the specified cache.
+     *
+     * @param cache                     The cache
+     * @param authenticationToken       The authentication
+     * @return                          The one time use token
+     */
+    private String generateToken(final ConcurrentMap<CacheKey, String> cache, final OtpAuthenticationToken authenticationToken) {
+        if (cache.size() >= MAX_CACHE_SOFT_LIMIT) {
+            throw new IllegalStateException("The maximum number of single use tokens have been issued.");
+        }
+
+        // hash the authentication and build a cache key
+        final CacheKey cacheKey = new CacheKey(hash(authenticationToken));
+
+        // store the token unless the token is already stored which should not update it's original timestamp
+        cache.putIfAbsent(cacheKey, authenticationToken.getName());
+
+        // return the token
+        return cacheKey.getToken();
+    }
+
+    /**
+     * Gets the corresponding authentication for the specified one time use token. The specified token will be removed.
+     *
+     * @param cache                     The cache
+     * @param token                     The one time use token
+     * @return                          The authenticated identity
+     */
+    private String getAuthenticationFromToken(final ConcurrentMap<CacheKey, String> cache, final String token) throws OtpAuthenticationException {
+        final String authenticatedUser = cache.remove(new CacheKey(token));
+        if (authenticatedUser == null) {
+            throw new OtpAuthenticationException("Unable to validate the access token.");
+        }
+
+        return authenticatedUser;
+    }
+
+    /**
+     * Hashes the specified authentication token. The resulting value will be used as the one time use token.
+     *
+     * @param authenticationToken   the authentication token
+     * @return                      the one time use token
+     */
+    private String hash(final OtpAuthenticationToken authenticationToken) {
+        try {
+            // input is the user identity and timestamp
+            final String input = authenticationToken.getName() + "-" + System.nanoTime();
+
+            // create the secret using secure random
+            final SecureRandom secureRandom = new SecureRandom();
+            final byte[] randomBytes = new byte[32];
+            secureRandom.nextBytes(randomBytes);
+            final SecretKeySpec secret = new SecretKeySpec(randomBytes, HMAC_SHA256); // 256 bit
+
+            // hash the input
+            final Mac hmacSha256 = Mac.getInstance(HMAC_SHA256);
+            hmacSha256.init(secret);
+            final byte[] output = hmacSha256.doFinal(input.getBytes(StandardCharsets.UTF_8));
+
+            // return the result as a base 64 string
+            return Base64.encodeBase64URLSafeString(output);
+        } catch (final NoSuchAlgorithmException | InvalidKeyException e) {
+            final String errorMessage = "There was an error generating the OTP";
+            logger.error(errorMessage, e);
+            throw new IllegalStateException("Unable to generate single use token.");
+        }
+    }
+
+    /**
+     * Key for the cache. Necessary to override the default String.equals() to utilize MessageDigest.isEquals() to prevent timing attacks.
+     */
+    private static class CacheKey {
+        final String token;
+
+        public CacheKey(String token) {
+            this.token = token;
+        }
+
+        public String getToken() {
+            return token;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            final CacheKey otherCacheKey = (CacheKey) o;
+            return MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8), otherCacheKey.token.getBytes(StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public int hashCode() {
+            return token.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return "CacheKey{token ending in '..." + token.substring(token.length() - 6) + "'}";
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
index 35c371d..693d420 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
@@ -21,7 +21,7 @@ import org.apache.nifi.web.security.user.NewAccountRequest;
 /**
  * An authentication token that is used as an authorization request when submitting a new account.
  */
-public class NewAccountAuthorizationRequestToken extends NiFiAuthortizationRequestToken {
+public class NewAccountAuthorizationRequestToken extends NiFiAuthorizationRequestToken {
 
     final NewAccountRequest newAccountRequest;
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.java
new file mode 100644
index 0000000..c20aaf3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.token;
+
+import java.util.Collections;
+import java.util.List;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * An authentication token that is used as an authorization request. The request has already been authenticated and is now going to be authorized.
+ * The request chain is specified during creation and is used authorize the user(s).
+ */
+public class NiFiAuthorizationRequestToken extends AbstractAuthenticationToken {
+
+    private final List<String> chain;
+
+    public NiFiAuthorizationRequestToken(final List<String> chain) {
+        super(null);
+        this.chain = chain;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return chain;
+    }
+
+    public List<String> getChain() {
+        return Collections.unmodifiableList(chain);
+    }
+
+    @Override
+    public final void setAuthenticated(boolean authenticated) {
+        throw new IllegalArgumentException("Cannot change the authenticated state.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.java
deleted file mode 100644
index a1459a4..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.web.security.token;
-
-import java.util.Collections;
-import java.util.List;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-
-/**
- * An authentication token that is used as an authorization request. The request has already been authenticated and is now going to be authorized.
- * The request chain is specified during creation and is used authorize the user(s).
- */
-public class NiFiAuthortizationRequestToken extends AbstractAuthenticationToken {
-
-    private final List<String> chain;
-
-    public NiFiAuthortizationRequestToken(final List<String> chain) {
-        super(null);
-        this.chain = chain;
-    }
-
-    @Override
-    public Object getCredentials() {
-        return null;
-    }
-
-    @Override
-    public Object getPrincipal() {
-        return chain;
-    }
-
-    public List<String> getChain() {
-        return Collections.unmodifiableList(chain);
-    }
-
-    @Override
-    public final void setAuthenticated(boolean authenticated) {
-        throw new IllegalArgumentException("Cannot change the authenticated state.");
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.java
new file mode 100644
index 0000000..fc51912
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.token;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * This is an Authentication Token for logging in. Once a user is authenticated, they can be issued an ID token.
+ */
+public class OtpAuthenticationToken extends AbstractAuthenticationToken {
+
+    private final String identity;
+
+    /**
+     * Creates a representation of the otp authentication token for a user.
+     *
+     * @param identity   The unique identifier for this user
+     */
+    public OtpAuthenticationToken(final String identity) {
+        super(null);
+        setAuthenticated(true);
+        this.identity = identity;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return identity;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("OtpAuthenticationToken for ")
+                .append(getName())
+                .toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
index 2c792f6..019a53c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
@@ -24,7 +24,7 @@ import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.NiFiAuthenticationFilter;
 import org.apache.nifi.web.security.ProxiedEntitiesUtils;
 import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
 import org.apache.nifi.web.security.user.NewAccountRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
     private X509IdentityProvider certificateIdentityProvider;
 
     @Override
-    public NiFiAuthortizationRequestToken attemptAuthentication(final HttpServletRequest request) {
+    public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
         // only suppport x509 login when running securely
         if (!request.isSecure()) {
             return null;
@@ -64,7 +64,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
         if (isNewAccountRequest(request)) {
             return new NewAccountAuthorizationRequestToken(new NewAccountRequest(proxyChain, getJustification(request)));
         } else {
-            return new NiFiAuthortizationRequestToken(proxyChain);
+            return new NiFiAuthorizationRequestToken(proxyChain);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
index d079293..44a93af 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
@@ -49,7 +49,10 @@
     <bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService">
         <constructor-arg ref="userService"/>
     </bean>
-    
+
+    <!-- otp service -->
+    <bean id="otpService" class="org.apache.nifi.web.security.otp.OtpService"/>
+
     <!-- login identity provider -->
     <bean id="loginIdentityProvider" class="org.apache.nifi.web.security.spring.LoginIdentityProviderFactoryBean">
         <property name="properties" ref="nifiProperties"/>