using quarkus jwt token in micronaut application - jwt

I have two microservices
auth-service developed using Quarkus to generate JWT token.
i developed the 2nd service using Micronaut (service1). I need to authenticate end points using auth-service. Can anyone please explain how to achieve it.
Please find the both services in the following
https://github.com/microservices-j/auth-service
https://github.com/microservices-j/service1
I generated the token using auth-service
and i pass the token into service1, but i am getting unauthorized.
> > Task :run __ __ _ _ | \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ | |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| | | | | | (__| | | (_) | | | | (_| | |_| | |_ |_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| Micronaut (v3.6.3)
>
> 12:49:25.382 [main] DEBUG i.m.s.a.AuthenticationModeCondition -
> CookieBasedAuthenticationModeCondition is not fulfilled because
> micronaut.security.authentication is not one of [cookie, idtoken].
> 12:49:25.678 [main] INFO io.micronaut.runtime.Micronaut - Startup
> completed in 1157ms. Server Running: http://localhost:8082
> 12:50:13.738 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.a.AuthenticationModeCondition -
> CookieBasedAuthenticationModeCondition is not fulfilled because
> micronaut.security.authentication is not one of [cookie, idtoken].
> 12:50:13.755 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in
> Authorization header 12:50:13.755 [default-nioEventLoopGroup-1-2]
> DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /swagger-ui,
> no token found. 12:50:13.759 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.security.rules.IpPatternsRule - One or more of the IP patterns
> matched the host address [0:0:0:0:0:0:0:1]. Continuing request
> processing. 12:50:13.762 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.rules.AbstractSecurityRule - The given roles [[isAnonymous()]]
> matched one or more of the required roles [[isAnonymous()]]. Allowing
> the request 12:50:13.762 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.security.filters.SecurityFilter - Authorized request GET
> /swagger-ui. The rule provider
> io.micronaut.security.rules.ConfigurationInterceptUrlMapRule
> authorized the request. 12:50:14.734 [default-nioEventLoopGroup-1-2]
> DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token
> in Authorization header 12:50:14.734 [default-nioEventLoopGroup-1-2]
> DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET,
> /swagger/service1-0.0.yml, no token found. 12:50:14.735
> [default-nioEventLoopGroup-1-2] DEBUG
> i.m.security.rules.IpPatternsRule - One or more of the IP patterns
> matched the host address [0:0:0:0:0:0:0:1]. Continuing request
> processing. 12:50:14.736 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found
> for path [/swagger/service1-0.0.yml] and method [GET]. Searching in
> patterns with no defined method. 12:50:14.736
> [default-nioEventLoopGroup-1-2] DEBUG i.m.s.rules.InterceptUrlMapRule
> - Url map pattern found for path [/swagger/service1-0.0.yml]. Comparing roles. 12:50:14.736 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.rules.AbstractSecurityRule - The given roles [[isAnonymous()]]
> matched one or more of the required roles [[isAnonymous()]]. Allowing
> the request 12:50:14.736 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.security.filters.SecurityFilter - Authorized request GET
> /swagger/service1-0.0.yml. The rule provider
> io.micronaut.security.rules.ConfigurationInterceptUrlMapRule
> authorized the request. 12:50:38.395 [default-nioEventLoopGroup-1-2]
> DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token
> in Authorization header 12:50:38.395 [default-nioEventLoopGroup-1-2]
> DEBUG i.m.s.t.reader.DefaultTokenResolver - Token
> eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwbXMiLCJzdWIiOiJkZW1vLnVzZXIiLCJpYXQiOjE2NjMzNDY5OTcsImV4cCI6MTY2MzM0NzI5NywianRpIjoiOGM1Mzc2ODMtOGM4Yy00MjgyLWFiYWUtMTU2Yzg3MjgzNGZhIn0.SxCAUxryW315Q3WlRSk6PypUh9s6K-Wce3zrB5Hmycs
> found in request GET /hello 12:50:38.418
> [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.t.jwt.validator.JwtValidator - Validating signed JWT
> 12:50:38.473 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.security.filters.SecurityFilter - Attributes: sub=>demo.user,
> iss=>pms, exp=>Fri Sep 16 12:54:57 EDT 2022, iat=>Fri Sep 16 12:49:57
> EDT 2022, jti=>8c537683-8c8c-4282-abae-156c872834fa 12:50:38.474
> [default-nioEventLoopGroup-1-2] DEBUG
> i.m.security.rules.IpPatternsRule - One or more of the IP patterns
> matched the host address [0:0:0:0:0:0:0:1]. Continuing request
> processing. 12:50:38.474 [default-nioEventLoopGroup-1-2] DEBUG
> i.m.s.rules.AbstractSecurityRule - None of the given roles
> [[isAnonymous(), isAuthenticated()]] matched the required roles [[]].
> Rejecting the request 12:50:38.474 [default-nioEventLoopGroup-1-2]
> DEBUG i.m.security.filters.SecurityFilter - Unauthorized request GET
> /hello. The rule provider
> io.micronaut.security.rules.SecuredAnnotationRule rejected the
> request. <===========--> 85% EXECUTING [6m 23s]
> > :run
> > IDLE

In your Quarkus application your are generating a signed JWT.
ApplicationScoped
#PermitAll
#Path("/login")
class LoginResource {
#Produces(MediaType.TEXT_PLAIN)
#Path("/")
#POST
fun login() : Response {
val token = Jwt
.issuer("pms")
.subject("demo.user")
.sign()
return Response.ok(token).build()
}
}
In your Quarks application properties you use a Public/Private key pair to sign the JWT.
quarkus.http.port=8081
smallrye.jwt.sign.key.location=/Users/bala/labs/auth-service/jwt/privateKey.pem
mp.jwt.verify.issuer=demo
mp.jwt.verify.publickey.location=/Users/bala/labs/auth-service/jwt/publicKey.pem
But in the Micronaut microservice you are trying to validate the JWT using a secret to validate the JWT.
micronaut:
server:
port: 8082
application:
name: service1
security:
authentication: bearer
basic-auth:
enabled: false # <-- Add this to disable basic auth
token:
jwt:
signatures:
secret:
generator:
secret: ${JWT_GENERATOR_SIGNATURE_SECRET:pleaseChangeThisSecretForANewOne}
Of course this fails.
You have the following two options.
The easy one. Make Quarkus generate signed JWT using a secret.
The other option. Use the Public Key in Micronaut to validate the JWT (see RSASignatureConfiguration)
For option 2) you have to add an addition bean to the application context.
#Singleton
#Named
class MySignatureConfig : RSASignatureConfiguration {
override fun getPublicKey(): RSAPublicKey {
val encoded: ByteArray = Base64.getDecoder().decode("<your public key as a string>")
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
val keySpec = X509EncodedKeySpec(encoded)
return keyFactory.generatePublic(keySpec) as RSAPublicKey
}
}
This will help to validate the JWT.
Additionally this is how Micronaut Security works, in the Basic Authentication Flow. Simply exchange AuthenticationFetcher with TokenAuthenticationFetcher. The helps debugging the flow.

Related

Keycloak JS Policy $evaluation is Undefined

I am creating a JS custom policy for keycloak following the guide:
https://www.keycloak.org/docs/latest/authorization_services/index.html#_policy_js
Unfortunately in my script all variables are undefined for some reaseon.
js
var context = $evaluation.context;
var identity = context.identity;
var permission = $evaluation.permission;
var resource = permission.resource;
var attributes = identity.getAttributes();
print('**** evaluation ' + JSON.stringify($evaluation));
print('**** context ' + JSON.stringify(context));
print('**** identity ' + JSON.stringify(identity));
print('**** attributes ' + JSON.stringify(attributes));
if (attributes.owner == identity.id) {
$evaluation.grant();
}
Keycloak logs:
keycloak-authorization-keycloak-1 | 2022-12-30 14:52:46,319 WARN [org.keycloak.connections.httpclient.DefaultHttpClientFactory] (executor-thread-2) TruststoreProvider is disabled
keycloak-authorization-keycloak-1 | 2022-12-30 14:52:48,087 WARN [org.keycloak.services.managers.AuthenticationManager] (executor-thread-1) Required action provider factory 'CONFIGURE_RECOVERY_AUTHN_CODES' configured in the realm 'myrealm' is not available. Provider not found or feature is disabled.
keycloak-authorization-keycloak-1 | 2022-12-30 14:52:48,088 WARN [org.keycloak.services.managers.AuthenticationManager] (executor-thread-1) Required action provider factory 'UPDATE_EMAIL' configured in the realm 'myrealm' is not available. Provider not found or feature is disabled.
keycloak-authorization-keycloak-1 | **** evaluation undefined
keycloak-authorization-keycloak-1 | **** context undefined
keycloak-authorization-keycloak-1 | **** identity undefined
keycloak-authorization-keycloak-1 | **** attributes undefined
services:
keycloak:
image: quay.io/keycloak/keycloak:latest
volumes:
- keycloak:/opt/keycloak/data
- ./js-policies/target/js-policies.jar:/opt/keycloak/providers/js-policies.jar
ports:
- 9000:8080
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
command:
- start-dev
It turns out the evaluation context is not null - it was JSON.stringify that was returning null for some reason.
I suppose it is due to a limitation of the Java Nashorn Engine.
If you are curious - the $evaluation gets mapped to the class DefaultEvaluation. From there on you can deduce all possible proeprties and values you can use in your policy by consulting the javadoc in the bellow link.
https://www.keycloak.org/docs-api/18.0/javadocs/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.html

Path variables in Apache Camel rest route

I would like to use path variables in my REST route URL in the following way:
rest().post("/companies/{companyId}/branches/branchId={branchId}");
The companyId can be properly retrieved as header, however, "branchId={branchId}" is treated as literal string. Therefore request to:
/companies/100/branches/branchId=200 - will return 404 not found.
but
/companies/100/branches/branchId={branchId} - will enter the route.
I would like to use branchId as header, in the same way as companyId, without having to change the structure of the URL.
Every path or query parameter can be received through the headers in Apache Camel. You can retrieve the headers with using the simple expression;
${header.your-header}
So you can retrieve the parameters as below;
parameter
simple expression
companyId
${header.companyId}
branchId
${header.branchId}
Here is a working Route example that I've made for you;
package com.bzdgn.camelso.route;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
public class RestEndpointRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
rest()
.post("/companies/{companyId}/branches/{branchId}")
.consumes("text/plain")
.produces("text/plain")
.to("direct:handle-post");
from("direct:handle-post")
.id("post-route")
.log("Received Body: ${body}")
.log("Company Id: ${header.companyId}")
.log("Branch Id: ${header.branchId}")
.setBody(simple("CompanyId = ${header.companyId} \nBranchId = ${header.branchId}"))
.convertBodyTo(String.class);
restConfiguration()
.component("netty-http")
.host("localhost")
.port("8080")
.bindingMode(RestBindingMode.auto);
}
}
The logs will be as below if you send a HTTP Post Request to the designated URL;
2021-05-28 21:13:30 INFO HttpServerBootstrapFactory:53 - BootstrapFactory on port 8080 is using bootstrap configuration: [NettyServerBootstrapConfiguration{protocol='http', host='localhost', port=8080, broadcast=false, sendBufferSize=65536, receiveBufferSize=65536, receiveBufferSizePredictor=0, workerCount=0, bossCount=1, keepAlive=true, tcpNoDelay=true, reuseAddress=true, connectTimeout=10000, backlog=0, serverInitializerFactory=org.apache.camel.component.netty.http.HttpServerInitializerFactory#ae3540e, nettyServerBootstrapFactory=null, options=null, ssl=false, sslHandler=null, sslContextParameters='null', needClientAuth=false, enabledProtocols='TLSv1,TLSv1.1,TLSv1.2, keyStoreFile=null, trustStoreFile=null, keyStoreResource='null', trustStoreResource='null', keyStoreFormat='JKS', securityProvider='SunX509', passphrase='null', bossGroup=null, workerGroup=null, networkInterface='null', reconnect='true', reconnectInterval='10000'}]
2021-05-28 21:13:30 INFO NettyComponent:164 - Creating shared NettyConsumerExecutorGroup with 17 threads
2021-05-28 21:13:31 INFO SingleTCPNettyServerBootstrapFactory:182 - ServerBootstrap binding to localhost:8080
2021-05-28 21:13:33 INFO NettyConsumer:77 - Netty consumer bound to: localhost:8080
2021-05-28 21:13:33 INFO AbstractCamelContext:2983 - Routes startup summary (total:2 started:2)
2021-05-28 21:13:33 INFO AbstractCamelContext:2988 - Started post-route (direct://handle-post)
2021-05-28 21:13:33 INFO AbstractCamelContext:2988 - Started route1 (rest://post:/companies/%7BcompanyId%7D/branches/%7BbranchId%7D)
2021-05-28 21:13:33 INFO AbstractCamelContext:3000 - Apache Camel 3.10.0 (camel-1) started in 3s337ms (build:93ms init:274ms start:2s970ms)
2021-05-28 21:13:35 INFO post-route:166 - Received Body: Example text body here
2021-05-28 21:13:35 INFO post-route:166 - Company Id: my-custom-company
2021-05-28 21:13:35 INFO post-route:166 - Branch Id: my-custom-branch-id
And you can test it via postman like this. The request, and the headers listed in the below images, and do not forget to add "Content Type: text/plain", because that's defined in the route that it will consume and produce "text/plain".

Ejabberd - ejabberd_auth_external:failure:103 External authentication program failed when calling 'check_password'

I already have a schema of users with authentication-key and wanted to do authentication via that. I tried implementing authentication via sql but due to different structure of my schema I was getting error and so I implemented external-authentication method. The technologies and OS used in my application are :
Node.JS
Ejabberd as XMPP server
MySQL Database
React-Native (Front-End)
OS - Ubuntu 18.04
I implemented the external authentication configuration as mentioned in https://docs.ejabberd.im/admin/configuration/#external-script and took php script https://www.ejabberd.im/files/efiles/check_mysql.php.txt as an example. But I am getting the below mentioned error in error.log. In ejabberd.yml I have done following configuration.
...
host_config:
"example.org.co":
auth_method: [external]
extauth_program: "/usr/local/etc/ejabberd/JabberAuth.class.php"
auth_use_cache: false
...
Also, is there any external auth javascript script?
Here is the error.log and ejabberd.log as mentioned below
error.log
2019-03-19 07:19:16.814 [error]
<0.524.0>#ejabberd_auth_external:failure:103 External authentication
program failed when calling 'check_password' for admin#example.org.co:
disconnected
ejabberd.log
2019-03-19 07:19:16.811 [debug] <0.524.0>#ejabberd_http:init:151 S:
[{[<<"api">>],mod_http_api},{[<<"admin">>],ejabberd_web_admin}]
2019-03-19 07:19:16.811 [debug]
<0.524.0>#ejabberd_http:process_header:307 (#Port<0.13811>) http
query: 'POST' <<"/api/register">>
2019-03-19 07:19:16.811 [debug]
<0.524.0>#ejabberd_http:process:394 [<<"api">>,<<"register">>] matches
[<<"api">>]
2019-03-19 07:19:16.811 [info]
<0.364.0>#ejabberd_listener:accept:238 (<0.524.0>) Accepted connection
::ffff:ip -> ::ffff:ip
2019-03-19 07:19:16.814 [info]
<0.524.0>#mod_http_api:log:548 API call register
[{<<"user">>,<<"test">>},{<<"host">>,<<"example.org.co">>},{<<"password">>,<<"test">>}]
from ::ffff:ip
2019-03-19 07:19:16.814 [error]
<0.524.0>#ejabberd_auth_external:failure:103 External authentication
program failed when calling 'check_password' for admin#example.org.co:
disconnected
2019-03-19 07:19:16.814 [debug]
<0.524.0>#mod_http_api:extract_auth:171 Invalid auth data:
{error,invalid_auth}
Any help regarding this topic will be appreciated.
1) Your config about the auth_method looks good.
2) Here is a python script I've used and upgraded to make an external authentication for ejabberd.
#!/usr/bin/python
import sys
from struct import *
import os
def openAuth(args):
(user, server, password) = args
# Implement your interactions with your service / database
# Return True or False
return True
def openIsuser(args):
(user, server) = args
# Implement your interactions with your service / database
# Return True or False
return True
def loop():
switcher = {
"auth": openAuth,
"isuser": openIsuser,
"setpass": lambda(none): True,
"tryregister": lambda(none): False,
"removeuser": lambda(none): False,
"removeuser3": lambda(none): False,
}
data = from_ejabberd()
to_ejabberd(switcher.get(data[0], lambda(none): False)(data[1:]))
loop()
def from_ejabberd():
input_length = sys.stdin.read(2)
(size,) = unpack('>h', input_length)
return sys.stdin.read(size).split(':')
def to_ejabberd(result):
if result:
sys.stdout.write('\x00\x02\x00\x01')
else:
sys.stdout.write('\x00\x02\x00\x00')
sys.stdout.flush()
if __name__ == "__main__":
try:
loop()
except error:
pass
I didn't created the communication with Ejabberd from_ejabberd() and to_ejabberd(), and unfortunately can't find back the sources.

Apiary Error when invoking API

I get the following error on ApiaryWe are sorry, but the API call failed.
My host is defined as
FORMAT: 1A
HOST: https://test.mynetwork.com/
GET CALL IS DEFINED AS
data urn [models/v2/files/{urn}/data{?guid}]
GET [GET]
Parameters
urn (required, string, ttt)...design urn.
guid (optional, string,067e6162-3b6f-4ae2-a171-2470b63dfe02)...filter by guid.
Response 200 (application/vnd.api+json)
Body
data
{
"version": "1.0",
}
When i invoke this , i get error . Any inputs
I have edited your API Blueprint as follows:
FORMAT: 1A
HOST: https://test.mynetwork.com
# Test API
This is a test API
## data urn [/models/v2/files/{urn}/data{?guid}]
+ Parameters
+ urn: `ttt` (required, string) - design urn
+ guid: `067e6162-3b6f-4ae2-a171-2470b63dfe02` (optional, string) - filter by guid
### test [GET]
+ Response 200 (application/vnd.api+json)
{
"version": "1.0"
}
You can find tutorials here: https://help.apiary.io/
Also not sure what you mean "invoke" the API - are you calling the mock server or are you hitting your own server through Apiary.
Happy to help further via the support option in Apiary itself or email us on support [at] apiary.io.

Grails Spring Security REST Plugin - Token Storage Failure

I'm setting up a Grails project with the Spring Security REST Plugin and I'm having some trouble. When I make the following request to /api/login with valid username and password
Accept: application/json
Content-Type: application/json
{
"username": "validuser",
"password": "validpassword"
}
I get the following exceptions
Error 2014-08-09 11:30:04,839 [http-bio-8080-exec-6] ERROR [/myphotoid-api].[default] - Servlet.service() for servlet [default] in context with path [/myphotoid-api] threw exception
Message: java.lang.Class cannot be cast to java.lang.String
Line | Method
->> 38 | storeToken in com.odobo.grails.plugin.springsecurity.rest.token.storage.GormTokenStorageService
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 97 | doFilter in com.odobo.grails.plugin.springsecurity.rest.RestAuthenticationFilter
| 82 | doFilter . in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
| 63 | doFilter in com.odobo.grails.plugin.springsecurity.rest.RestLogoutFilter
| 82 | doFilter . in com.brandseye.cors.CorsFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run in java.lang.Thread
and then my client receives a 302 to /login/auth, the regular stateful login page. :(
However if I make the following request to /api/login with an invalid username and password
Accept: application/json
Content-Type: application/json
{
"username": "validuser",
"password": "badpassword"
}
I get a 401, which I guess is what I should expect.
Here is the valid section from my Config.groovy
// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.campuscardtools.myphotoid.Person'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.campuscardtools.myphotoid.PersonRole'
grails.plugin.springsecurity.authority.className = 'com.campuscardtools.myphotoid.Role'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
'/api/login': ['permitAll'],
'/': ['permitAll'],
'/index': ['permitAll'],
'/index.gsp': ['permitAll'],
'/assets/**': ['permitAll']
]
grails.plugin.springsecurity.rest.token.storage.useGorm = true
grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = com.campuscardtools.myphotoid.AuthenticationToken
grails.plugin.springsecurity.filterChain.chainMap = [
'/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter', // Stateless chain
'/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter' // Traditional chain
]
Thank you in advance for your help!
#kau Thanks for the helpful comment.
Looks like your tokenDomainClassName needs to be enclosed within quotes – kau Aug 22 at 14:01
so I changed this
grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = com.campuscardtools.myphotoid.AuthenticationToken
to this
grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = 'com.campuscardtools.myphotoid.AuthenticationToken'
Check plugin confiugraiton section in documentation : http://alvarosanchez.github.io/grails-spring-security-rest/docs/guide/configuration.html
You have to configure chains properly in grails.plugin.springsecurity.filterChain.chainMap:
grails.plugin.springsecurity.filterChain.chainMap = [
'/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter', // Stateless chain
'/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter' // Traditional chain
]