MongooseIM has a provision to use JWT instead of username and password for authorization.
On the server-side, the docs suggest to modify the mongooseim.toml file (can be found at /etc/mongooseim/mongooseim.toml)
[auth]
methods = ["jwt"]
[auth.jwt]
secret.value = "top-secret123"
algorithm = "HS256"
username_key = "user"
But how does then one authenticate from Gajim or from Java code?
Let's first understand what is happening behind the scenes.
Instead of passing the username-password pair. We create a JWT token and send that. JWT tokens are stateless, which means if one has the secret key, one can decode and encode the token to/from the original message.
Here is a working code in Java. We generate the JWT token and send that token instead of the password. To generate the JWT token, we have used Auth0 (you will need to add this in classpath). Link to the maven project.
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import javax.net.ssl.X509TrustManager;
import java.net.InetAddress;
import java.util.Date;
public class JWTMain {
private final static String senderUsername = "jatin";
private final static String senderPassword = "abcd";
private final static String sendTo = "dad";
public static void main(String[] args) throws Exception {
Algorithm algorithm = Algorithm.HMAC256("top-secret123");
String token = JWT.create()
.withClaim("user", senderUsername) // they key needs to match with `username_key` in mongooseim.toml file
.withClaim(senderUsername, senderPassword)
.sign(algorithm);
System.out.println("Token generated: " + token);
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setSecurityMode(ConnectionConfiguration.SecurityMode.required)
.setUsernameAndPassword("jatin", token)
.setXmppDomain(JidCreate.domainBareFrom("localhost"))
.setHostAddress(InetAddress.getByName("localhost"))
.setPort(5222)
.setCustomX509TrustManager(new TrustAllManager())
.addEnabledSaslMechanism("PLAIN")
.build();
AbstractXMPPConnection connection = new XMPPTCPConnection(config);
AbstractXMPPConnection connect = connection.connect();
connection.login();
sendMessage("This message is being sent programmatically? " + new Date(), sendTo + "#localhost", connect);
}
private static void sendMessage(String body, String toJid, AbstractXMPPConnection mConnection) throws Exception {
Jid jid = JidCreate.from(toJid);
Chat chat = ChatManager.getInstanceFor(mConnection)
.chatWith(jid.asEntityBareJidIfPossible());
chat.send(body);
System.out.println("Message sent to : " + toJid);
}
}
class TrustAllManager implements X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}
If you wish to login to Gajim with the JWT token:
The above program outputs the JWT token. You can use that token and provide the token in the password field.
Related
I am trying to generate token using MSAL4j-1.8 jar in my Java application.
Below is the code I am using :
private static IAuthenticationResult getAccessTokenByClientCredentialGrant() throws Exception {
ConfidentialClientApplication app = ConfidentialClientApplication.builder(
clientId,
ClientCredentialFactory.createFromSecret(secret))
.authority(authority)
.build();
// With client credentials flows the scope is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal), and then granted by a tenant administrator
ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
Collections.singleton(scope))
.build();
CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);
return future.get();
}
I get an error :
Caused by: com.microsoft.aad.msal4j.MsalServiceException: AADSTS50049: Unknown or invalid instance.
Trace ID: c6a936bf-2b0f-489e-ada3-d2311e708500
Correlation ID: f515dd78-7915-43d7-9020-62631f27c955
Timestamp: 2020-12-10 16:14:09Z
at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.validate(AadInstanceDiscoveryProvider.java:147)
at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.doInstanceDiscoveryAndCache(AadInstanceDiscoveryProvider.java:138)
at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.getMetadataEntry(AadInstanceDiscoveryProvider.java:42)
at com.microsoft.aad.msal4j.AuthenticationResultSupplier.getAuthorityWithPrefNetworkHost(AuthenticationResultSupplier.java:32)
at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:59)
at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:59)
at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:17)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1601)
at java.lang.Thread.run(Thread.java:818)
Any idea on what can be the problem?
As #juunas asked, seems there is something wrong with your authority setting.
Try the code below:
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IAuthenticationResult;
public class AADClientCred {
public static void main(String[] args) {
String tenantID = "<your tenant ID or name>";
String clientID = "<your Azure AD app id>";
String Secret = "<your azure ad app secret>";
String authority = "https://login.microsoftonline.com/" + tenantID;
//I use microsoft graph as resource for demo
String scope = "https://graph.microsoft.com/.default";
try {
String access_token = getAccessTokenByClientCredentialGrant(clientID, Secret, authority, scope)
.accessToken();
System.out.println("access token : " + access_token);
} catch (Exception e) {
e.printStackTrace();
}
}
private static IAuthenticationResult getAccessTokenByClientCredentialGrant(String clientID, String Secret,
String authority, String scope) throws Exception {
ConfidentialClientApplication app = ConfidentialClientApplication
.builder(clientID, ClientCredentialFactory.createFromSecret(Secret)).authority(authority).build();
ClientCredentialParameters clientCredentialParam = ClientCredentialParameters
.builder(Collections.singleton(scope)).build();
CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);
return future.get();
}
}
Result:
Let me know if you have any further questions.
I want to use access token to get userinfo with a java open-id connect library the same as nodejs.
I use npm-openid-client to get the userInfo and it works very well in nodejs
/**
** client_id and client_secret can be empty now
*/
const { Issuer } = require('openid-client');
const end_point = 'xxx'
const access_token = 'xxx'
Issuer.discover(end_point).then(function (issuer) {
const client = new issuer.Client({
client_id: 'xx',
client_secret: 'xx',
});
client.userinfo(access_token).then(function (userinfo) {
console.log('userinfo %j', userinfo);
});
});
I google java open-id library and find some library from openid.net
and finally I use connect2id
I follow the link openid-connect/userinfo and write some code below:
import java.io.*;
import java.net.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.sdk.claims.*;
class Test {
public static void main (String[] args) throws Exception{
String uriStr = "";
String tokenStr = "";
URI userInfoEndpoint = new URI(uriStr);
BearerAccessToken token = new BearerAccessToken(tokenStr);
// Make the request
HTTPResponse httpResponse = new UserInfoRequest(userInfoEndpoint, token)
.toHTTPRequest()
.send();
// Parse the response
UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
if (! userInfoResponse.indicatesSuccess()) {
// The request failed, e.g. due to invalid or expired token
System.out.println(userInfoResponse.toErrorResponse().getErrorObject().getCode());
System.out.println(userInfoResponse.toErrorResponse().getErrorObject().getDescription());
return;
}
// Extract the claims
UserInfo userInfo = userInfoResponse.toSuccessResponse().getUserInfo();
System.out.println("Subject: " + userInfo.getSubject());
System.out.println("Email: " + userInfo.getEmailAddress());
System.out.println("Name: " + userInfo.getName());
}
}
the result is that httpResponse return 404 not found. how can I fix it and get the userInfo ?
I'm using spring boot to build a simple auth process for my app.
I have AuthorizationServerConfig & ResourceServerConfig setup, my frontend is a SPA. When I hit /oauth/token route, I got a JWT back which I previously stored in localStorage, and when I try to hit the resource server route, I have authorization header setup with this JWT, everything works.
But now I want to do authorization with JWT stored in the cookie, how I can config it so that it works with my current authorization/resource server config? I googled for a while and the best I can find is to set up a customize token extractor, but I'm not sure how to get it right, thank you in advance.
-------------- update --------------
I have #EnableAuthorizationServer and #EnableResourceServer on, and the EnableResourceServer setup an OAuthAuthenticationProcessingFilter automatically, this filter user bearer header authentication which uses a bearer token extractor to extract from the request header, I looked at the source code, it's hardcoded into the library, how I can customize this filter to extract JWT from the cookie?
Read cookie value from the request object and parse jwt manually.
Here is sample code
public Jws<Claims> parseJWT(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, "Token cookie name");
if(cookie == null) {
throw new SecurityException("Token not found from cookies");
}
String token = cookie.getValue();
return Jwts.parser().setSigningKey("your signing Key").parseClaimsJws(token);
}
you can create request filter and check jwt.
There are many implementation for JWT. Am using this. io.jsonwebtoken
I am adding a Token Helper Class which has methods to validate, generate, refresh token. You can focus on the JWT extraction part.
Jar Dependency
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
JWT Helper Class. It contains methods to validate, refresh, generate token.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.test.dfx.common.TimeProvider;
import com.test.dfx.model.LicenseDetail;
import com.test.dfx.model.User;
#Component
public class TokenHelper {
protected final Log LOGGER = LogFactory.getLog(getClass());
#Value("${app.name}")
private String APP_NAME;
#Value("${jwt.secret}")
public String SECRET; // Secret key used to generate Key. Am getting it from propertyfile
#Value("${jwt.expires_in}")
private int EXPIRES_IN; // can specify time for token to expire.
#Value("${jwt.header}")
private String AUTH_HEADER;
#Autowired
TimeProvider timeProvider;
private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512; // JWT Algorithm for encryption
public Date getIssuedAtDateFromToken(String token) {
Date issueAt;
try {
final Claims claims = this.getAllClaimsFromToken(token);
issueAt = claims.getIssuedAt();
} catch (Exception e) {
LOGGER.error("Could not get IssuedDate from passed token");
issueAt = null;
}
return issueAt;
}
public String getAudienceFromToken(String token) {
String audience;
try {
final Claims claims = this.getAllClaimsFromToken(token);
audience = claims.getAudience();
} catch (Exception e) {
LOGGER.error("Could not get Audience from passed token");
audience = null;
}
return audience;
}
public String refreshToken(String token) {
String refreshedToken;
Date a = timeProvider.now();
try {
final Claims claims = this.getAllClaimsFromToken(token);
claims.setIssuedAt(a);
refreshedToken = Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
} catch (Exception e) {
LOGGER.error("Could not generate Refresh Token from passed token");
refreshedToken = null;
}
return refreshedToken;
}
public String generateToken(String username) {
String audience = generateAudience();
return Jwts.builder()
.setIssuer( APP_NAME )
.setSubject(username)
.setAudience(audience)
.setIssuedAt(timeProvider.now())
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
}
private Claims getAllClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("Could not get all claims Token from passed token");
claims = null;
}
return claims;
}
private Date generateExpirationDate() {
long expiresIn = EXPIRES_IN;
return new Date(timeProvider.now().getTime() + expiresIn * 1000);
}
public int getExpiredIn() {
return EXPIRES_IN;
}
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username != null &&
username.equals(userDetails.getUsername()) &&
!isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
public String getToken( HttpServletRequest request ) {
/**
* Getting the token from Authentication header
* e.g Bearer your_token
*/
String authHeader = getAuthHeaderFromHeader( request );
if ( authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
public String getAuthHeaderFromHeader( HttpServletRequest request ) {
return request.getHeader(AUTH_HEADER);
}
}
Finally your controller class
public void validateToken(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, "TOKEN_NAME");
if(cookie == null) {
throw new SecurityException("JWT token missing");
}
String token = cookie.getValue(); // JWT Token
Claims claims = TokenHelper.getAllClaimsFromToken(token); // claims will be null if Token is invalid
}
I am attempting to make a web app where the user is required to login via Reddit. For this I am using JRAW, where the main object used is RedditClient.
I am slightly confused how I should handle tracking these multiple clients for the users who are logged in. I have a working application, but I think I'm going about storing these the wrong way.
Auth.class
public class Auth {
private static final String URL = "http://localhost:4200/";
private final UUID id = UUID.randomUUID();
private final RedditClient redditClient = getDefaultRedditClient();
private final Credentials credentials = getWebappCreds();
private final URL authUrl = getClientAuthURL();
public UUID getId(){
return id;
}
public URL getAuthUrl(){
return authUrl;
}
public AuthStatus getOAuthStatus(){
return redditClient.getOAuthHelper().getAuthStatus();
}
#JsonIgnore
public RedditClient getRedditClient(){
return redditClient;
}
#JsonIgnore
public Credentials getWebappCreds(){
return Credentials.webapp("<WEB_APP_ID>", "<WEB_APP_SECRET>", URL);
}
private URL getClientAuthURL(){
URL url = redditClient.getOAuthHelper().getAuthorizationUrl(credentials, true, "history");
return url;
}
public void auth(String state, String code) throws NetworkException, OAuthException, IllegalStateException {
System.out.println("auth - state: " + state + ", code: " + code);
String url = URL + "?state=" + state + "&code=" + code;
System.out.println("auth - url: " + url);
auth(url);
}
private void auth(String redirectURL) throws NetworkException, OAuthException, IllegalStateException {
OAuthData data = redditClient.getOAuthHelper().onUserChallenge(redirectURL, credentials);
redditClient.authenticate(data);
}
private static RedditClient getDefaultRedditClient(){
UserAgent myUserAgent = UserAgent.of("desktop", "io.rj93.reddit.search", "v0.1", "rj93");
return new RedditClient(myUserAgent);
}
}
AuthController.class
package io.rj93.reddit.search.server;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import net.dean.jraw.http.NetworkException;
import net.dean.jraw.http.oauth.OAuthException;
#RestController
#RequestMapping("api/v1")
#CrossOrigin
public class AuthController {
private static ConcurrentMap<String, Auth> auths = new ConcurrentHashMap<String, Auth>();
#RequestMapping("/auth")
public Auth getAuth(){
Auth auth = new Auth();
auths.put(auth.getId().toString(), auth);
return auth;
}
#RequestMapping(value = "/auth", method = RequestMethod.POST)
public Auth authenticate(#RequestBody Map<String, String> payload) throws NetworkException, OAuthException, IllegalStateException{
Auth auth = auths.get(payload.get("id"));
auth.auth(payload.get("state"), payload.get("code"));
return auth;
}
}
The way it works is the website requests a new auth at getAuth, which creates a new reddit client and begins the auth process. This is stored in a Map, with the key being a UUID generated, which is returned to the user and sent in all further requests to the server. The website redirects to reddit to allow the user to grant permissions, and is then redirected to my website, where the values to authenticate are sent to the server in authenticate.
How should I actually be storing this? The library doesn't allow me to instantiate a RedditClient using preset data, so I have to go through this process of redirecting to reddit, etc.
We're trying to get 2-legged OAuth to work with the Gmail atom feed. We're using the Java library contributed by John Kristian, Praveen Alavilli and Dirk Balfanz. [http://oauth.net/code/] instead of the GData library.
We know we have the correct CONSUMER_KEY and CONSUMER_SECRET, etc. becuase it works with the Contacts feed (http://www.google.com/m8/feeds/contacts/default/full) and have no problems. However with Gmail atom feed it always returns: HTTP/1.1 401 Unauthorized
Any ideas? Should we try a different OAuth framework or does the problem lie on the Google side?
We think we got it working with the OAuth libraries but not with the GData library.
Snippet of code is:
import static net.oauth.OAuth.HMAC_SHA1;
import static net.oauth.OAuth.OAUTH_SIGNATURE_METHOD;
import java.net.URL;
import java.util.List;
import java.util.Map;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.ParameterStyle;
import net.oauth.SimpleOAuthValidator;
import net.oauth.client.OAuthClient;
import net.oauth.client.httpclient4.HttpClient4;
/**
* Sample application demonstrating how to do 2-Legged OAuth in the Google Data
* Java Client. See the comments below to learn about the details.
*
*/
public class GmailAtomFeed2LeggedOauth {
public static String CONSUMER_KEY = "test-1001.com";
public static String CONSUMER_SECRET = "zN0ttehR3#lSecr3+";
public static String SCOPE = "https://mail.google.com/mail/feed/atom";
public static String RESOURCE_URL = "https://mail.google.com/mail/feed/atom";
public static String SERVICE_NAME = "mail";
public static String username = "username";
public static boolean debug = true;
public static void main(String[] args) throws Exception {
// This should be passed in as a parameter
String user = username + "#" + CONSUMER_KEY;
OAuthConsumer consumer = new OAuthConsumer(null, CONSUMER_KEY, CONSUMER_SECRET, null);
OAuthAccessor accessor = new OAuthAccessor(consumer);
// HMAC uses the access token secret as a factor,
// and it's a little less compute-intensive than RSA.
accessor.consumer.setProperty(OAUTH_SIGNATURE_METHOD, HMAC_SHA1);
// Gmail only supports an atom feed
URL atomFeedUrl = new URL(SCOPE +"?xoauth_requestor_id=" + user);
System.out.println("=====================================================");
System.out.println("Building new request message...");
OAuthMessage request = accessor.newRequestMessage(OAuthMessage.GET, atomFeedUrl.toString(),null);
if (debug) {
List<Map.Entry<String, String>> params = request.getParameters();
for (Map.Entry<String, String> p : params) {
System.out.println("'" + p.getKey() + "' = <" + p.getValue() + ">");
}
System.out.println("Validating message...");
SimpleOAuthValidator validator=new SimpleOAuthValidator();
validator.validateMessage(request,accessor);
}
OAuthClient client = new OAuthClient(new HttpClient4());
System.out.println("Client invoking request message...");
System.out.println(" request: " + request);
OAuthMessage message = client.invoke(request, ParameterStyle.AUTHORIZATION_HEADER);
System.out.println("=====================================================");
System.out.println(" message: " + message.readBodyAsString());
System.out.println("=====================================================");
}
}
Put the OAuth data in the Authorization header, not on the URI.