starting to structure the project, impl to do
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..49ca981
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.idea
+*.iml
+target
+
diff --git a/geronimo-jwt-auth-impl/pom.xml b/geronimo-jwt-auth-impl/pom.xml
new file mode 100644
index 0000000..d58ce23
--- /dev/null
+++ b/geronimo-jwt-auth-impl/pom.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="
+         http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>geronimo-jwt-auth</artifactId>
+    <groupId>org.apache.geronimo</groupId>
+    <version>1.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>geronimo-jwt-auth-impl</artifactId>
+  <name>Geronimo JWT Auth :: Impl</name>
+
+  <properties>
+    <tck.version>1.1-SNAPSHOT</tck.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-el-api</artifactId>
+      <version>9.0.7</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.microprofile.config</groupId>
+      <artifactId>microprofile-config-api</artifactId>
+      <version>1.2</version>
+      <scope>provided</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo</groupId>
+      <artifactId>geronimo-microprofile-jwt-auth-spec</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jacc_1.1_spec</artifactId>
+      <version>1.0.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.arquillian.testng</groupId>
+      <artifactId>arquillian-testng-container</artifactId>
+      <version>1.1.13.Final</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+      <version>6.8.21</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.meecrowave</groupId>
+      <artifactId>meecrowave-arquillian</artifactId>
+      <version>1.2.1</version>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.inject</groupId>
+          <artifactId>javax.inject</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.microprofile.jwt</groupId>
+      <artifactId>microprofile-jwt-auth-tck</artifactId>
+      <version>${tck.version}</version>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.enterprise</groupId>
+          <artifactId>cdi-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.microprofile.jwt</groupId>
+      <artifactId>microprofile-jwt-auth-tck</artifactId>
+      <version>${tck.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.21.0</version>
+        <configuration>
+          <suiteXmlFiles>
+            <suiteXmlFile>${project.basedir}/src/test/resources/tck.xml</suiteXmlFile>
+          </suiteXmlFiles>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/JwtException.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/JwtException.java
new file mode 100644
index 0000000..7457d3f
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/JwtException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth;
+
+public class JwtException extends RuntimeException {
+    private final int status;
+
+    public JwtException(final String msg, final int status) {
+        super(msg);
+        this.status = status;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/cdi/GeronimoJwtAuthExtension.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/cdi/GeronimoJwtAuthExtension.java
new file mode 100644
index 0000000..8316b65
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/cdi/GeronimoJwtAuthExtension.java
@@ -0,0 +1,378 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.cdi;
+
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
+import static java.util.Optional.ofNullable;
+import static java.util.function.Function.identity;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collector;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.enterprise.util.Nonbinding;
+import javax.inject.Provider;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.spi.JsonProvider;
+import javax.servlet.ServletException;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.servlet.JwtRequest;
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+public class GeronimoJwtAuthExtension implements Extension {
+    private final ThreadLocal<JwtRequest> request = new ThreadLocal<>();
+
+    private final Collection<Injection> injectionPoints = new HashSet<>(8);
+    private final Collection<Throwable> errors = new ArrayList<>();
+    private JsonProvider json;
+
+    void setClaimMethodsBinding(@Observes final BeforeBeanDiscovery beforeBeanDiscovery) {
+        beforeBeanDiscovery.configureQualifier(Claim.class)
+                .methods().forEach(m -> m.remove(it -> it.annotationType() == Nonbinding.class));
+        json = JsonProvider.provider();
+    }
+
+    void vetoDefaultClaimQualifier(@Observes final ProcessAnnotatedType<Claim> processAnnotatedType) {
+        processAnnotatedType.veto();
+    }
+
+    void captureInjections(@Observes final ProcessInjectionPoint<?, ?> processInjectionPoint) {
+        final InjectionPoint injectionPoint = processInjectionPoint.getInjectionPoint();
+        ofNullable(injectionPoint.getAnnotated().getAnnotation(Claim.class))
+                .flatMap(claim -> createInjection(claim, injectionPoint.getType()))
+                .ifPresent(injectionPoints::add);
+    }
+
+    void addClaimBeans(@Observes final AfterBeanDiscovery afterBeanDiscovery) {
+        afterBeanDiscovery.addBean()
+                .id(GeronimoJwtAuthExtension.class.getName() + "#" + JsonWebToken.class.getName())
+                .beanClass(JsonWebToken.class)
+                .types(JsonWebToken.class, Object.class)
+                .qualifiers(Default.Literal.INSTANCE)
+                .scope(ApplicationScoped.class)
+                .createWith(ctx -> {
+                    final JwtRequest request = this.request.get();
+                    if (request == null) {
+                        throw new IllegalStateException("No JWT in this request");
+                    }
+                    return request.getToken();
+                });
+
+        injectionPoints.forEach(injection ->
+                afterBeanDiscovery.addBean()
+                        .id(GeronimoJwtAuthExtension.class.getName() + "#" + injection.getId())
+                        .beanClass(injection.findClass())
+                        .qualifiers(injection.literal())
+                        .scope(injection.findScope())
+                        .types(injection.type, Object.class)
+                        .createWith(ctx -> injection.createInstance(request.get())));
+
+        injectionPoints.clear();
+    }
+
+    void afterDeployment(@Observes final AfterDeploymentValidation afterDeploymentValidation) {
+        errors.forEach(afterDeploymentValidation::addDeploymentProblem);
+    }
+
+    private Optional<Injection> createInjection(final Claim claim, final Type type) {
+        if (ParameterizedType.class.isInstance(type)) {
+            final ParameterizedType pt = ParameterizedType.class.cast(type);
+            if (pt.getActualTypeArguments().length == 1) {
+                final Type raw = pt.getRawType();
+                final Type arg = pt.getActualTypeArguments()[0];
+
+                if (raw == Provider.class || raw == Instance.class) {
+                    return createInjection(claim, arg);
+                }
+                if (raw == Optional.class) {
+                    return createInjection(claim, arg)
+                            .map(it -> new Injection(claim.value(), claim.standard(), type) {
+                                @Override
+                                Object createInstance(final JwtRequest jwtRequest) {
+                                    return ofNullable(it.createInstance(jwtRequest));
+                                }
+                            });
+                }
+                if (raw == ClaimValue.class) {
+                    final String name = getClaimName(claim);
+                    return createInjection(claim, arg)
+                            .map(it -> new Injection(claim.value(), claim.standard(), type) {
+                                @Override
+                                Object createInstance(final JwtRequest jwtRequest) {
+                                    return new ClaimValue<Object>() {
+                                        @Override
+                                        public String getName() {
+                                            return name;
+                                        }
+
+                                        @Override
+                                        public Object getValue() {
+                                            return it.createInstance(jwtRequest);
+                                        }
+                                    };
+                                }
+                            });
+                }
+                if (Class.class.isInstance(raw) && Collection.class.isAssignableFrom(Class.class.cast(raw))) {
+                    return of(new Injection(claim.value(), claim.standard(), type));
+                }
+            }
+        } else if (Class.class.isInstance(type)) {
+            final String name = getClaimName(claim);
+            final Class<?> clazz = Class.class.cast(type);
+            if (JsonValue.class.isAssignableFrom(clazz)) {
+                if (JsonString.class.isAssignableFrom(clazz)) {
+                    return of(new Injection(claim.value(), claim.standard(), clazz) {
+                        @Override
+                        Object createInstance(final JwtRequest jwtRequest) {
+                            final Object instance = super.createInstance(jwtRequest);
+                            if (JsonString.class.isInstance(instance)) {
+                                return instance;
+                            }
+                            return json.createValue(String.class.cast(instance));
+                        }
+                    });
+                }
+                if (JsonNumber.class.isAssignableFrom(clazz)) {
+                    return of(new Injection(claim.value(), claim.standard(), clazz) {
+                        @Override
+                        Object createInstance(final JwtRequest jwtRequest) {
+                            final Object instance = super.createInstance(jwtRequest);
+                            if (JsonNumber.class.isInstance(instance)) {
+                                return instance;
+                            }
+                            return json.createValue(Number.class.cast(instance).doubleValue());
+                        }
+                    });
+                }
+                if (JsonObject.class.isAssignableFrom(clazz)) {
+                    return of(new Injection(claim.value(), claim.standard(), clazz));
+                }
+                if (JsonArray.class.isAssignableFrom(clazz)) {
+                    return of(new Injection(claim.value(), claim.standard(), clazz) {
+                        @Override
+                        Object createInstance(final JwtRequest jwtRequest) {
+                            final Object instance = super.createInstance(jwtRequest);
+                            if (JsonArray.class.isInstance(instance)) {
+                                return instance;
+                            }
+                            if (Set.class.isInstance(instance)) {
+                                return ((Set<String>) instance).stream()
+                                        .collect(Collector.of(
+                                                        json::createArrayBuilder,
+                                                        JsonArrayBuilder::add,
+                                                        JsonArrayBuilder::addAll,
+                                                        JsonArrayBuilder::build));
+                            }
+                            throw new IllegalArgumentException("Unsupported value: " + instance);
+                        }
+                    });
+                }
+            } else {
+                final Class<?> objectType = wrapPrimitives(clazz);
+                if (CharSequence.class.isAssignableFrom(clazz) || Double.class.isAssignableFrom(objectType) ||
+                        Long.class.isAssignableFrom(objectType) || Integer.class.isAssignableFrom(objectType)) {
+                    return of(new Injection(claim.value(), claim.standard(), objectType));
+                }
+            }
+        }
+        errors.add(new IllegalArgumentException(type + " not supported by JWT-Auth implementation"));
+        return empty();
+    }
+
+    private Class<?> wrapPrimitives(final Class<?> type) {
+        if (long.class == type) {
+            return Long.class;
+        }
+        if (int.class == type) {
+            return Integer.class;
+        }
+        if (double.class == type) {
+            return Double.class;
+        }
+        return type;
+    }
+
+    private static String getClaimName(final Claim claim) {
+        return getClaimName(claim.value(), claim.standard());
+    }
+
+    private static String getClaimName(final String name, final Claims val) {
+        return of(name).filter(s -> !s.isEmpty()).orElse(val.name());
+    }
+
+    public void execute(final JwtRequest req, final ServletRunnable task) throws ServletException, IOException {
+        request.set(req); // we want to track it ourself to support propagation properly when needed
+        try {
+            task.run();
+        } finally {
+            request.remove();
+        }
+    }
+
+    @FunctionalInterface
+    public interface ServletRunnable {
+        void run() throws ServletException, IOException;
+    }
+
+    private static class Injection {
+        private final String name;
+        private final Claims claims;
+        private final Type type;
+        private final int hash;
+        private final Function<Object, Object> transformer;
+        private final String runtimeName;
+
+        private Injection(final String name, final Claims claims, final Type type) {
+            this.name = name;
+            this.claims = claims;
+            this.type = type;
+
+            Function<Object, Object> transformer;
+            try {
+                Claims.valueOf(getClaimName(name, claims));
+                transformer = identity();
+            } catch (final IllegalArgumentException iae) {
+                if (type == String.class) {
+                    transformer = val -> JsonString.class.cast(val).getString();
+                } else if (type == Long.class) {
+                    transformer = val -> JsonNumber.class.cast(val).longValue();
+                } else {
+                    transformer = identity();
+                }
+            }
+            this.transformer = transformer;
+            this.runtimeName = getClaimName(name, claims);
+
+            {
+                int result = name.hashCode();
+                result = 31 * result + claims.hashCode();
+                hash = 31 * result + type.hashCode();
+            }
+        }
+
+        private String getId() {
+            return name + "/" + claims + "/" + type;
+        }
+
+        private Class<?> findClass() {
+            if (Class.class.isInstance(type)) {
+                return Class.class.cast(type);
+            }
+            if (ParameterizedType.class.isInstance(type)) {
+                ParameterizedType current = ParameterizedType.class.cast(type);
+                while (!Class.class.isInstance(current.getRawType())) {
+                    current = ParameterizedType.class.cast(current.getRawType());
+                }
+                return Class.class.cast(current.getRawType());
+            }
+            throw new IllegalArgumentException("Can't find a class from " + type);
+        }
+
+        private Class<? extends Annotation> findScope() {
+            return Dependent.class;
+        }
+
+        private Annotation literal() {
+            return new ClaimLiteral(name, claims);
+        }
+
+        Object createInstance(final JwtRequest jwtRequest) {
+            return transformer.apply(jwtRequest.getToken().getClaim(runtimeName));
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final Injection injection = Injection.class.cast(o);
+            return name.equals(injection.name) && claims == injection.claims && type.equals(injection.type);
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public String toString() {
+            return "Injection{" +
+                    "claim='" + ofNullable(name).filter(n -> !n.isEmpty()).orElse(claims.name()) +
+                    "', type=" + type +
+                    '}';
+        }
+    }
+
+    private static class ClaimLiteral extends AnnotationLiteral<Claim> implements Claim {
+        private final String name;
+        private final Claims claims;
+
+        private ClaimLiteral(final String name, final Claims claims) {
+            this.name = name;
+            this.claims = claims;
+        }
+
+        @Override
+        public String value() {
+            return name;
+        }
+
+        @Override
+        public Claims standard() {
+            return claims;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString().replace(", ", ", ");
+        }
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/GeronimoJwtAuthConfig.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/GeronimoJwtAuthConfig.java
new file mode 100644
index 0000000..2a79393
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/GeronimoJwtAuthConfig.java
@@ -0,0 +1,30 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.config;
+
+@FunctionalInterface
+public interface GeronimoJwtAuthConfig {
+    String read(String value, String def);
+
+    static GeronimoJwtAuthConfig create() {
+        try {
+            return new JwtAuthConfigMpConfigImpl();
+        } catch (final NoClassDefFoundError | ExceptionInInitializerError cnfe) {
+            return System::getProperty;
+        }
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/JwtAuthConfigMpConfigImpl.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/JwtAuthConfigMpConfigImpl.java
new file mode 100644
index 0000000..6566277
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/config/JwtAuthConfigMpConfigImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.config;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+
+public class JwtAuthConfigMpConfigImpl implements GeronimoJwtAuthConfig {
+    private final Config config;
+
+    public JwtAuthConfigMpConfigImpl() {
+        config = ConfigProvider.getConfig();
+    }
+
+    @Override
+    public String read(final String key, final String def) {
+        return config.getOptionalValue(key, String.class).orElse(def);
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/GeronimoJsonWebToken.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/GeronimoJsonWebToken.java
new file mode 100644
index 0000000..bc009e2
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/GeronimoJsonWebToken.java
@@ -0,0 +1,101 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.jwt;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Set;
+import java.util.stream.Stream;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+
+import org.eclipse.microprofile.jwt.Claims;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+class GeronimoJsonWebToken implements JsonWebToken {
+    private final JsonObject delegate;
+    private final String raw;
+
+    GeronimoJsonWebToken(final String raw, final JsonObject delegate) {
+        this.raw = raw;
+        this.delegate = delegate;
+    }
+
+    @Override
+    public String getName() {
+        return getClaim(Claims.upn.name());
+    }
+
+    @Override
+    public Set<String> getClaimNames() {
+        return delegate.keySet();
+    }
+
+    @Override
+    public <T> T getClaim(final String claimName) {
+        try {
+            final Claims claim = Claims.valueOf(claimName);
+            if (claim == Claims.raw_token) {
+                return (T) raw;
+            }
+            if (claim.getType() == String.class) {
+                return (T) delegate.getString(claimName);
+            }
+            if (claim.getType() == Long.class) {
+                return (T) Long.valueOf(delegate.getJsonNumber(claimName).longValue());
+            }
+            if (claim.getType() == JsonObject.class) {
+                return (T) delegate.getJsonObject(claimName);
+            }
+            if (claim.getType() == Set.class) {
+                final JsonValue jsonValue = delegate.get(claimName);
+                if (jsonValue.getValueType() == JsonValue.ValueType.ARRAY) {
+                    return (T) JsonArray.class.cast(jsonValue).stream()
+                            .map(this::toString)
+                            .collect(toSet());
+                }
+                if (jsonValue.getValueType() == JsonValue.ValueType.STRING) {
+                    return (T) Stream.of(JsonString.class.cast(jsonValue).getString().split(","))
+                            .collect(toSet());
+                }
+                return (T) jsonValue;
+            }
+            return (T) delegate.get(claimName);
+        } catch (final IllegalArgumentException iae) {
+            return (T) delegate.get(claimName);
+        }
+    }
+
+    private String toString(final Object value) {
+        if (JsonString.class.isInstance(value)) {
+            return JsonString.class.cast(value).getString();
+        }
+        if (JsonNumber.class.isInstance(value)) {
+            return String.valueOf(JsonNumber.class.cast(value).doubleValue());
+        }
+        return value.toString();
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/JwtService.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/JwtService.java
new file mode 100644
index 0000000..c0fc52b
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/jwt/JwtService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.jwt;
+
+import static java.util.Collections.emptyMap;
+
+import java.io.ByteArrayInputStream;
+import java.util.Base64;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReaderFactory;
+
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+@ApplicationScoped
+public class JwtService {
+    private JsonReaderFactory readerFactory;
+
+    @PostConstruct
+    private void init() {
+        readerFactory = Json.createReaderFactory(emptyMap());
+    }
+
+    public JsonWebToken parse(final String jwt) {
+        // TODO
+        final String[] split = jwt.split("\\.");
+        if (split.length != 3) {
+            // fail
+        }
+        // sign, date validation etc but without lib please, use GeronimoJwtAuthConfig to read how in postconstruct
+
+        final byte[] token = Base64.getUrlDecoder().decode(split[1]);
+        final JsonObject json = readerFactory.createReader(new ByteArrayInputStream(token)).readObject();
+        return new GeronimoJsonWebToken(jwt, json);
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthFilter.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthFilter.java
new file mode 100644
index 0000000..cb21ab1
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthFilter.java
@@ -0,0 +1,83 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.servlet;
+
+import java.io.IOException;
+
+import javax.enterprise.inject.spi.CDI;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.JwtException;
+import org.apache.geronimo.microprofile.impl.jwtauth.cdi.GeronimoJwtAuthExtension;
+import org.apache.geronimo.microprofile.impl.jwtauth.config.GeronimoJwtAuthConfig;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.JwtService;
+
+public class GeronimoJwtAuthFilter implements Filter {
+    private String headerName;
+    private String prefix;
+    private JwtService service;
+    private GeronimoJwtAuthExtension extension;
+
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        final GeronimoJwtAuthConfig config = GeronimoJwtAuthConfig.class.cast(filterConfig.getServletContext().getAttribute(GeronimoJwtAuthConfig.class.getName()));
+        headerName = config.read("geronimo.jwt-auth.header.name", "Authorization");
+        prefix = config.read("geronimo.jwt-auth.header.prefix", "bearer") + " ";
+
+        final CDI<Object> current = CDI.current();
+        service = current.select(JwtService.class).get();
+        extension = current.select(GeronimoJwtAuthExtension.class).get();
+    }
+
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        if (!HttpServletRequest.class.isInstance(request)) {
+            chain.doFilter(request, response);
+            return;
+        }
+        try {
+            final JwtRequest req = new JwtRequest(service, headerName, prefix, HttpServletRequest.class.cast(request));
+            extension.execute(req, () -> chain.doFilter(req, response));
+        } catch (final Exception e) { // when not used with JAX-RS but directly Servlet
+            Throwable current = e;
+            while (current != null) {
+                if (JwtException.class.isInstance(current)) {
+                    final JwtException ex = JwtException.class.cast(current);
+                    HttpServletResponse.class.cast(response).sendError(ex.getStatus(), ex.getMessage());
+                    return;
+                }
+                if (current == current.getCause()) {
+                    break;
+                }
+                current = current.getCause();
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void destroy() {
+        // no-op
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthInitializer.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthInitializer.java
new file mode 100644
index 0000000..6941f78
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/GeronimoJwtAuthInitializer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.servlet;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.ws.rs.core.Application;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.config.GeronimoJwtAuthConfig;
+import org.eclipse.microprofile.auth.LoginConfig;
+
+@HandlesTypes(LoginConfig.class)
+public class GeronimoJwtAuthInitializer implements ServletContainerInitializer {
+    @Override
+    public void onStartup(final Set<Class<?>> classes, final ServletContext ctx) throws ServletException {
+        final GeronimoJwtAuthConfig appConfig = GeronimoJwtAuthConfig.create();
+        ctx.setAttribute(GeronimoJwtAuthConfig.class.getName(), appConfig);
+
+        ofNullable(classes).filter(c -> !c.isEmpty()).ifPresent(marked -> marked.stream()
+                .filter(Application.class::isAssignableFrom) // needed?
+                .map(it -> it.getAnnotation(LoginConfig.class))
+                .filter(it -> "MP-JWT".equalsIgnoreCase(it.authMethod()))
+                .sorted(Comparator.comparing(LoginConfig::realmName)) // to be deterministic
+                .findFirst()
+                .ifPresent(config -> {
+                    final FilterRegistration.Dynamic filter = ctx.addFilter("geronimo-microprofile-jwt-auth-filter", GeronimoJwtAuthFilter.class);
+                    filter.setAsyncSupported(true);
+                    filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, appConfig.read("geronimo.jwt-auth.servlet.filter.mapping", "/*"));
+                }));
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/JwtRequest.java b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/JwtRequest.java
new file mode 100644
index 0000000..a151c06
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/java/org/apache/geronimo/microprofile/impl/jwtauth/servlet/JwtRequest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.geronimo.microprofile.impl.jwtauth.servlet;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toList;
+
+import java.security.Principal;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.function.Supplier;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.geronimo.microprofile.impl.jwtauth.JwtException;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.JwtService;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+public class JwtRequest extends HttpServletRequestWrapper {
+    private final HttpServletRequest delegate;
+    private final Supplier<JsonWebToken> tokenExtractor;
+    private volatile JsonWebToken token; // cache for perf reasons
+
+    JwtRequest(final JwtService service, final String header, final String prefix, final HttpServletRequest request) {
+        super(request);
+        this.delegate = request;
+
+        this.tokenExtractor = () -> {
+            if (token != null) {
+                return token;
+            }
+
+            synchronized (this) {
+                if (token != null) {
+                    return token;
+                }
+
+                final String auth = delegate.getHeader(header);
+                if (auth == null || auth.isEmpty()) {
+                    throw new JwtException("No " + header + " header", HttpServletResponse.SC_UNAUTHORIZED);
+                }
+                if (!auth.toLowerCase(Locale.ROOT).startsWith(prefix)) {
+                    throw new JwtException("No prefix " + prefix + " in header " + header, HttpServletResponse.SC_UNAUTHORIZED);
+                }
+                token = service.parse(auth.substring(prefix.length()));
+                setAttribute(JsonWebToken.class.getName(), token);
+                return token;
+            }
+        };
+
+        // integration hook if needed
+        setAttribute(JsonWebToken.class.getName() + ".supplier", tokenExtractor);
+        // not portable but used by some servers like tomee
+        setAttribute("javax.security.auth.subject.callable", (Callable<Subject>) () -> {
+            final Set<Principal> principals = new LinkedHashSet<>();
+            final JsonWebToken namePrincipal = tokenExtractor.get();
+            principals.add(namePrincipal);
+            principals.addAll(namePrincipal.getGroups().stream().map(role -> (Principal) () -> role).collect(toList()));
+            return new Subject(true, principals, emptySet(), emptySet());
+        });
+    }
+
+    public JsonWebToken getToken() {
+        return tokenExtractor.get();
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        return tokenExtractor.get();
+    }
+
+    @Override
+    public boolean isUserInRole(final String role) {
+        return tokenExtractor.get().getGroups().contains(role);
+    }
+
+    @Override
+    public String getAuthType() {
+        return "MP-JWT";
+    }
+}
diff --git a/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..1e16141
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.apache.geronimo.microprofile.impl.jwtauth.cdi.GeronimoJwtAuthExtension
diff --git a/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..a929cc4
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.apache.geronimo.microprofile.impl.jwtauth.servlet.GeronimoJwtAuthInitializer
diff --git a/geronimo-jwt-auth-impl/src/test/resources/META-INF/openwebbeans/openwebbeans.properties b/geronimo-jwt-auth-impl/src/test/resources/META-INF/openwebbeans/openwebbeans.properties
new file mode 100644
index 0000000..9cd2756
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/test/resources/META-INF/openwebbeans/openwebbeans.properties
@@ -0,0 +1,2 @@
+# todo: OWB default is wrong
+org.apache.webbeans.container.InjectionResolver.fastMatching = false
diff --git a/geronimo-jwt-auth-impl/src/test/resources/dev.xml b/geronimo-jwt-auth-impl/src/test/resources/dev.xml
new file mode 100644
index 0000000..b0e1289
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/test/resources/dev.xml
@@ -0,0 +1,25 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+    Licensed 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.
+-->
+<suite name="Microprofile-jwt-auth-TCK (dev)" verbose="1" configfailurepolicy="continue" >
+  <test name="Tests">
+    <classes>
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.PrimitiveInjectionTest">
+        <methods>
+          <include name="verifyInjectedAudience"/>
+          <include name="verifyInjectedGroups"/>
+        </methods>
+      </class>
+    </classes>
+  </test>
+</suite>
diff --git a/geronimo-jwt-auth-impl/src/test/resources/tck.xml b/geronimo-jwt-auth-impl/src/test/resources/tck.xml
new file mode 100644
index 0000000..b928eff
--- /dev/null
+++ b/geronimo-jwt-auth-impl/src/test/resources/tck.xml
@@ -0,0 +1,47 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+    Licensed 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.
+-->
+<suite name="Microprofile-jwt-auth-TCK" verbose="1" configfailurepolicy="continue" >
+  <test name="Tests">
+    <groups>
+      <define name="base-groups">
+        <include name="arquillian" description="Arquillian internal"/>
+        <include name="utils" description="Utility tests"/>
+        <include name="jwt" description="Base JsonWebToken tests"/>
+        <include name="jaxrs" description="JAX-RS invocation tests"/>
+        <include name="cdi" description="Base CDI injection of ClaimValues"/>
+        <include name="cdi-json" description="CDI injection of JSON-P values"/>
+        <include name="cdi-provider" description="CDI injection of javax.inject.Provider values"/>
+      </define>
+      <define name="excludes">
+        <include name="debug" description="Internal debugging tests" />
+      </define>
+      <run>
+        <include name="base-groups" />
+        <exclude name="excludes" />
+      </run>
+    </groups>
+    <classes>
+      <class name="org.eclipse.microprofile.jwt.tck.util.TokenUtilsTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.UnsecuredPingTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.RequiredClaimsTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.ClaimValueInjectionTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.JsonValueInjectionTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.ProviderInjectionTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.RolesAllowedTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.InvalidTokenTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.PrimitiveInjectionTest" />
+      <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.PrincipalInjectionTest" />
+    </classes>
+  </test>
+</suite>
diff --git a/geronimo-microprofile-jwt-auth-spec/pom.xml b/geronimo-microprofile-jwt-auth-spec/pom.xml
new file mode 100644
index 0000000..c6622bf
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="
+          http://maven.apache.org/POM/4.0.0
+          http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>geronimo-jwt-auth</artifactId>
+    <groupId>org.apache.geronimo</groupId>
+    <version>1.0.0-SNAPSHOT</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>geronimo-microprofile-jwt-auth-spec</artifactId>
+  <name>Geronimo JWT Auth :: Spec 1.1</name>
+</project>
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/auth/LoginConfig.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/auth/LoginConfig.java
new file mode 100644
index 0000000..c71e6cd
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/auth/LoginConfig.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.eclipse.microprofile.auth;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Inherited
+@Documented
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface LoginConfig {
+    String authMethod();
+    String realmName() default "";
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claim.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claim.java
new file mode 100644
index 0000000..e17b2c2
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claim.java
@@ -0,0 +1,40 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.enterprise.util.Nonbinding;
+import javax.inject.Qualifier;
+
+@Qualifier
+@Retention(RUNTIME)
+@Target({FIELD, METHOD, PARAMETER, TYPE})
+public @interface Claim {
+    @Nonbinding
+    String value() default "";
+
+    @Nonbinding
+    Claims standard() default Claims.UNKNOWN;
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/ClaimValue.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/ClaimValue.java
new file mode 100644
index 0000000..dcd3c89
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/ClaimValue.java
@@ -0,0 +1,25 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import java.security.Principal;
+
+public interface ClaimValue<TYPE> extends Principal {
+    @Override
+    String getName();
+    TYPE getValue();
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claims.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claims.java
new file mode 100644
index 0000000..db1ee1e
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/Claims.java
@@ -0,0 +1,89 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import java.util.Set;
+
+import javax.json.JsonObject;
+
+// important: ensure to respect the spec order
+public enum Claims {
+    iss("Issuer", String.class),
+    sub("Subject", String.class),
+    exp("Expiration Time", Long.class),
+    iat("Issued At Time", Long.class),
+    jti("JWT ID", String.class),
+    upn("MP-JWT specific unique principal name", String.class),
+    groups("MP-JWT specific groups permission grant", Set.class),
+    raw_token("MP-JWT specific original bearer token", String.class),
+    aud("Audience", Set.class),
+    nbf("Not Before", Long.class),
+    auth_time("Time when the authentication occurred", Long.class),
+    updated_at("Time the information was last updated", Long.class),
+    azp("Authorized party - the party to which the ID Token was issued", String.class),
+    nonce("Value used to associate a Client session with an ID Token", String.class),
+    at_hash("Access Token hash value", Long.class),
+    c_hash("Code hash value", Long.class),
+    full_name("Full name", String.class),
+    family_name("Surname(s) or last name(s)", String.class),
+    middle_name("Middle name(s)", String.class),
+    nickname("Casual name", String.class),
+    given_name("Given name(s) or first name(s)", String.class),
+    preferred_username("Shorthand name by which the End-User wishes to be referred to", String.class),
+    email("Preferred e-mail address", String.class),
+    email_verified("True if the e-mail address has been verified; otherwise false", Boolean.class),
+    gender("Gender", String.class),
+    birthdate("Birthday", String.class),
+    zoneinfo("Time zone", String.class),
+    locale("Locale", String.class),
+    phone_number("Preferred telephone number", String.class),
+    phone_number_verified("True if the phone number has been verified; otherwise false", Boolean.class),
+    address("Preferred postal address", JsonObject.class),
+    acr("Authentication Context Class Reference", String.class),
+    amr("Authentication Methods References", String.class),
+    sub_jwk("Public key used to check the signature of an ID Token", JsonObject.class),
+    cnf("Confirmation", String.class),
+    sip_from_tag("SIP From tag header field parameter value", String.class),
+    sip_date("SIP Date header field value", String.class),
+    sip_callid("SIP Call-Id header field value", String.class),
+    sip_cseq_num("SIP CSeq numeric header field parameter value", String.class),
+    sip_via_branch("SIP Via branch header field parameter value", String.class),
+    orig("Originating Identity String", String.class),
+    dest("Destination Identity String", String.class),
+    mky("Media Key Fingerprint String", String.class),
+    jwk("JSON Web Key Representing Public Key", JsonObject.class),
+    jwe("Encrypted JSON Web Key", String.class),
+    kid("Key identifier", String.class),
+    jku("JWK Set URL", String.class),
+    UNKNOWN("A catch all for any unknown claim", Void.class);
+
+    private final String description;
+    private final Class<?> type;
+
+    Claims(String description, Class<?> type) {
+        this.description = description;
+        this.type = type;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/JsonWebToken.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/JsonWebToken.java
new file mode 100644
index 0000000..37a292a
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/JsonWebToken.java
@@ -0,0 +1,70 @@
+/*
+ * 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.eclipse.microprofile.jwt;
+
+import java.security.Principal;
+import java.util.Optional;
+import java.util.Set;
+
+public interface JsonWebToken extends Principal {
+    @Override
+    String getName();
+
+    default String getRawToken() {
+        return getClaim(Claims.raw_token.name());
+    }
+
+    default String getIssuer() {
+        return getClaim(Claims.iss.name());
+    }
+
+    default Set<String> getAudience() {
+        return getClaim(Claims.aud.name());
+    }
+
+    default String getSubject() {
+        return getClaim(Claims.sub.name());
+    }
+
+    default String getTokenID() {
+        return getClaim(Claims.jti.name());
+    }
+
+    default long getExpirationTime() {
+        return getClaim(Claims.exp.name());
+    }
+
+    default long getIssuedAtTime() {
+        return getClaim(Claims.iat.name());
+    }
+
+    default Set<String> getGroups() {
+        return getClaim(Claims.groups.name());
+    }
+
+    Set<String> getClaimNames();
+
+    default boolean containsClaim(String claimName) {
+        return claim(claimName).isPresent();
+    }
+
+    <T> T getClaim(String claimName);
+
+    default <T> Optional<T> claim(String claimName) {
+        return Optional.ofNullable(getClaim(claimName));
+    }
+}
diff --git a/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/config/Names.java b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/config/Names.java
new file mode 100644
index 0000000..bdae43b
--- /dev/null
+++ b/geronimo-microprofile-jwt-auth-spec/src/main/java/org/eclipse/microprofile/jwt/config/Names.java
@@ -0,0 +1,19 @@
+package org.eclipse.microprofile.jwt.config;
+
+public class Names {
+    public final static String VERIFIER_PUBLIC_KEY = "org.eclipse.microprofile.authentication.JWT.verifierPublicKey";
+
+    public final static String ISSUER = "org.eclipse.microprofile.authentication.JWT.issuer";
+
+    public final static String ISSUERS = "org.eclipse.microprofile.authentication.JWT.issuers";
+
+    public final static String CLOCK_SKEW = "org.eclipse.microprofile.authentication.JWT.clockSkew";
+
+    public final static String VERIFIER_JWKS_URI = "org.eclipse.microprofile.authentication.JWT.VERIFIER_JWKS_URI";
+
+    public final static String VERIFIER_JWKS_REFRESH_INTERVAL = "org.eclipse.microprofile.authentication.JWT.VERIFIER_JWKS_REFRESH_INTERVAL";
+
+    private Names() {
+        throw new AssertionError("don't call me");
+    }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0ea6dcf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="
+            http://maven.apache.org/POM/4.0.0
+            http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <modules>
+    <module>geronimo-microprofile-jwt-auth-spec</module>
+    <module>geronimo-jwt-auth-impl</module>
+  </modules>
+
+  <parent>
+    <groupId>org.apache</groupId>
+    <artifactId>apache</artifactId>
+    <version>18</version>
+  </parent>
+
+  <groupId>org.apache.geronimo</groupId>
+  <artifactId>geronimo-jwt-auth</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  <name>Geronimo JWT Auth</name>
+
+  <description>
+    Apache Geronimo implementation of the Microprofile JWT Auth Specification
+  </description>
+  <url>http://geronimo.apache.org/maven/geronimo-jwt-auth/${project.version}</url>
+
+  <distributionManagement>
+    <site>
+      <id>apache-website</id>
+      <url>${site.deploy.url}/maven/geronimo-jwt-auth/${project.version}</url>
+    </site>
+  </distributionManagement>
+
+  <scm>
+    <connection>scm:git:https://gitbox.apache.org/repos/asf/geronimo-jwt-auth.git</connection>
+    <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/geronimo-jwt-auth.git</developerConnection>
+    <url>https://gitbox.apache.org/repos/asf/geronimo-jwt-auth.git</url>
+  </scm>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-servlet-api</artifactId>
+      <version>9.0.7</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jaxrs_2.0_spec</artifactId>
+      <version>1.0-alpha-1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-json_1.1_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-atinject_1.0_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jcdi_2.0_spec</artifactId>
+      <version>1.0.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-annotation_1.3_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>