diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..b0c23e2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+**/.classpath
+.DS_Store
+**/nb-configuration.xml
+**/.springBeans
+**/testing.properties
+**/metalnx.testing.properties
+**/*.eml
+**/Scripts
+.idea
+*.iml
+.project
+.idea
+.metadata
+.DS_Store
+Servers
+.settings
+.classpath
+target
+mvn-repo
+bin
+*.war
+*.log
+*/nbactions.xml
+*.eml
+nbactions.xml
+testing.properties
+*/test-output
+JargonVersion.java
+**/${test.option.mount.basedir}
+**/*.*~
+*.*~
+.dbeaver*
+test.metalnx.properties
+**/.vscode/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100755
index 0000000..0772a1d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+### Added
+
+### Changed
+
+### Removed
+
diff --git a/LICENSE b/LICENSE
new file mode 100755
index 0000000..ce733ff
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2017, DataNet Federation Consortium
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..8645077
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+
+Jargon Extensions API for irods-ext
+
+
+# Project: Jargon Extensions JWT
+### Date:
+### Release Version: 4.3.1.0-SNAPSHOT
+### git tag:
+
+## Description
+
+Supports JWT creation and decoding for iRODS related microservices t
+
+## Requirements
+
+* Jargon depends on Java 1.8+
+* Jargon is built using Apache Maven2, see POM for dependencies
+
+## Libraries
+
+Jargon-core uses Maven for dependency management. See the pom.xml file for references to various dependencies.
+
+Note that the following bug and feature requests are logged in GitHub with related commit information [[https://github.com/DICE-UNC/jargon-extensions-jwt/issues]]
+
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..3e0ea0a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,151 @@
+
+
+ 4.0.0
+
+ org.irods
+ jargon-pom
+ 4.3.1.0-SNAPSHOT
+
+ irodsext-jwt-service
+ irodsext-jwt-service
+
+
+ junit
+ junit
+ compile
+
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ org.mockito
+ mockito-all
+ test
+
+
+
+
+ dice.repository snaps
+ dice.repository.snapshots
+ https://raw.github.com/DICE-UNC/DICE-Maven/master/snapshots
+
+ true
+
+
+ true
+ always
+ warn
+
+
+
+ dice.repository
+ dice.repository
+ https://raw.github.com/DICE-UNC/DICE-Maven/master/releases
+
+ true
+
+
+ false
+ always
+ warn
+
+
+
+ netbeans.repository
+ netbeans.repository
+ http://bits.netbeans.org/maven2
+
+ true
+
+
+ true
+ never
+ fail
+
+
+
+ Tools for managing jwts used by associated microservices
+
+
+
+ maven-antrun-plugin
+
+
+ 0
+ validate
+
+
+
+
+
+ test.confirm=${jargon.test.confirm}
+ test.data.directory=${jargon.test.data.directory}
+ test.irods.admin=${jargon.test.irods.admin}
+ test.irods.admin.password=${jargon.test.irods.admin.password}
+ test.irods.user=${jargon.test.irods.user}
+ test.irods.password=${jargon.test.irods.password}
+ test.irods.resource=${jargon.test.irods.resource}
+ test2.irods.user=${jargon.test.irods.user2}
+ test2.irods.password=${jargon.test.irods.password2}
+ test2.irods.resource=${jargon.test.irods.resource2}
+ test3.irods.user=${jargon.test.irods.user3}
+ test3.irods.password=${jargon.test.irods.password3}
+ test3.irods.resource=${jargon.test.irods.resource3}
+ test.irods.host=${jargon.test.irods.host}
+ test.irods.port=${jargon.test.irods.port}
+ test.irods.zone=${jargon.test.irods.zone}
+ test.resource.group=${jargon.test.resource.group}
+ test.irods.userDN=${jargon.test.irods.userDN}
+ test.irods.scratch.subdir=${jargon.test.irods.scratch.subdir}
+
+
+
+
+ run
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3.2
+
+ true
+
+
+
+
+
+
diff --git a/src/main/java/.gitinclude b/src/main/java/.gitinclude
new file mode 100755
index 0000000..e69de29
diff --git a/src/main/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImpl.java b/src/main/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImpl.java
new file mode 100644
index 0000000..36b0c05
--- /dev/null
+++ b/src/main/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImpl.java
@@ -0,0 +1,67 @@
+/**
+ *
+ */
+package org.irods.jargon.irodsext.jwt;
+
+import java.security.Key;
+import java.util.Date;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+
+/**
+ * @author Mike Conway - NIEHS
+ *
+ */
+public class JwtIssueServiceImpl {
+
+ public static final Logger log = LoggerFactory.getLogger(JwtIssueServiceImpl.class);
+
+ private final JwtServiceConfig jwtServiceConfig;
+ private final Key myKey;
+
+ /**
+ * Constructor with configs
+ *
+ * @param jwtServiceConfig {@link JwtServiceConfig}
+ */
+ public JwtIssueServiceImpl(final JwtServiceConfig jwtServiceConfig) {
+ if (jwtServiceConfig == null) {
+ throw new IllegalArgumentException("null jwtServiceConfig");
+ }
+
+ this.jwtServiceConfig = jwtServiceConfig;
+ myKey = Keys.hmacShaKeyFor(jwtServiceConfig.getSecret().getBytes());
+ }
+
+ public String issueJwtToken(final String subject) {
+ log.info("issueJwtToken()");
+
+ if (subject == null || subject.isEmpty()) {
+ throw new IllegalArgumentException("null or empty subject");
+ }
+
+ String signedJwt = Jwts.builder().setSubject(subject).setIssuer(jwtServiceConfig.getIssuer())
+ .setIssuedAt(new Date()).signWith(myKey).compact();
+ return signedJwt;
+
+ }
+
+ public Jws decodeJwtToken(final String token) {
+ log.info("decodeJwtToken()");
+
+ if (token == null || token.isEmpty()) {
+ throw new IllegalArgumentException("null or empty token");
+ }
+
+ Jws claims = Jwts.parser().setSigningKey(myKey).parseClaimsJws(token);
+ return claims;
+
+ }
+
+}
diff --git a/src/main/java/org/irods/jargon/irodsext/jwt/JwtServiceConfig.java b/src/main/java/org/irods/jargon/irodsext/jwt/JwtServiceConfig.java
new file mode 100644
index 0000000..89f3b8b
--- /dev/null
+++ b/src/main/java/org/irods/jargon/irodsext/jwt/JwtServiceConfig.java
@@ -0,0 +1,59 @@
+/**
+ *
+ */
+package org.irods.jargon.irodsext.jwt;
+
+/**
+ * Basic configs for a service to issue and decode jwts used in iRODS
+ * microservices
+ *
+ * @author Mike Conway - NIEHS
+ *
+ */
+public class JwtServiceConfig {
+
+ /**
+ * Issuer typically in reverse dns name format, used as "iss" in the JWT
+ */
+ private String issuer = "";
+ /**
+ * Secret used to sign tokens given the provided algo
+ */
+ private String secret = "";
+ /**
+ * Signing algo used in JWT
+ */
+ private String algo = "";
+
+ public String getIssuer() {
+ return issuer;
+ }
+
+ public void setIssuer(String issuer) {
+ this.issuer = issuer;
+ }
+
+ public String getSecret() {
+ return secret;
+ }
+
+ public void setSecret(String secret) {
+ this.secret = secret;
+ }
+
+ public String getAlgo() {
+ return algo;
+ }
+
+ public void setAlgo(String algo) {
+ this.algo = algo;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("JwtServiceConfig [issuer=").append(issuer).append(", algo=").append(algo).append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/org/irods/jargon/irodsext/jwt/package-info.java b/src/main/java/org/irods/jargon/irodsext/jwt/package-info.java
new file mode 100644
index 0000000..211f488
--- /dev/null
+++ b/src/main/java/org/irods/jargon/irodsext/jwt/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ * Utils and services to support use of JWT tokens in Jargon extensions and
+ * microservices
+ *
+ * @author Mike Conway - NIEHS
+ *
+ */
+package org.irods.jargon.irodsext.jwt;
\ No newline at end of file
diff --git a/src/main/resources/.gitinclude b/src/main/resources/.gitinclude
new file mode 100755
index 0000000..e69de29
diff --git a/src/test/java/.gitinclude b/src/test/java/.gitinclude
new file mode 100755
index 0000000..e69de29
diff --git a/src/test/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImplTest.java b/src/test/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImplTest.java
new file mode 100644
index 0000000..7b4d1b1
--- /dev/null
+++ b/src/test/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImplTest.java
@@ -0,0 +1,47 @@
+package org.irods.jargon.irodsext.jwt;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.WeakKeyException;
+
+public class JwtIssueServiceImplTest {
+
+ @Test(expected = WeakKeyException.class)
+ public void testIssueJwtWeakKey() {
+ JwtServiceConfig config = new JwtServiceConfig();
+ config.setAlgo(SignatureAlgorithm.HS256.getValue());
+ config.setIssuer("test");
+ config.setSecret("thisisasecret");
+ JwtIssueServiceImpl jwtIssueServiceImpl = new JwtIssueServiceImpl(config);
+ jwtIssueServiceImpl.issueJwtToken("subject");
+ }
+
+ @Test
+ public void testIssueJwt() {
+ JwtServiceConfig config = new JwtServiceConfig();
+ config.setAlgo(SignatureAlgorithm.HS256.getValue());
+ config.setIssuer("test");
+ config.setSecret("thisisasecretthatisverysecretyouwillneverguessthiskey");
+ JwtIssueServiceImpl jwtIssueServiceImpl = new JwtIssueServiceImpl(config);
+ String jwt = jwtIssueServiceImpl.issueJwtToken("subject");
+ Assert.assertNotNull("no jwt issued", jwt);
+ }
+
+ @Test
+ public void testIssueAndDecodeJwt() {
+ JwtServiceConfig config = new JwtServiceConfig();
+ config.setAlgo(SignatureAlgorithm.HS256.getValue());
+ config.setIssuer("test");
+ config.setSecret("thisisasecretthatisverysecretyouwillneverguessthiskeyhurray");
+ JwtIssueServiceImpl jwtIssueServiceImpl = new JwtIssueServiceImpl(config);
+ String jwt = jwtIssueServiceImpl.issueJwtToken("subject");
+ Jws actual = jwtIssueServiceImpl.decodeJwtToken(jwt);
+ Assert.assertNotNull("claims not returned", actual);
+
+ }
+
+}
diff --git a/src/test/java/org/irods/jargon/irodsext/jwt/unittest/AllTests.java b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/AllTests.java
new file mode 100644
index 0000000..a1770f5
--- /dev/null
+++ b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/AllTests.java
@@ -0,0 +1,12 @@
+package org.irods.jargon.irodsext.jwt.unittest;
+
+import org.irods.jargon.irodsext.jwt.JwtIssueServiceImplTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({ JwtIssueServiceImplTest.class })
+public class AllTests {
+
+}
diff --git a/src/test/java/org/irods/jargon/irodsext/jwt/unittest/package-info.java b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/package-info.java
new file mode 100644
index 0000000..ad82a96
--- /dev/null
+++ b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/package-info.java
@@ -0,0 +1,10 @@
+/**
+ *
+ */
+/**
+ * Suites and test utils
+ *
+ * @author Mike Conway - NIEHS
+ *
+ */
+package org.irods.jargon.irodsext.jwt.unittest;
\ No newline at end of file
diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties
new file mode 100755
index 0000000..eed7b65
--- /dev/null
+++ b/src/test/resources/log4j.properties
@@ -0,0 +1,11 @@
+# Set root logger level to DEBUG and its only appender to A1.
+#log4j.rootLogger=ERROR, A1
+log4j.category.org.irods.jargon.core=INFO, A1
+log4j.category.org.irods.jargon.datautils=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n