From JSON Web Token to Single Sign-On Part 1: Creating the Token
This is the first entry in a series of articles that offer a solution to Single Sign-on using the JSON Web Token (JWT) standard.
Join the DZone community and get the full member experience.
Join For FreeThis is the first entry in a series of articles that offer a method for building Single Sign-on using the JSON Web Token (JWT) standard1. In this entry, you will learn how to implement an authentication service that generates encrypted tokens based on user credentials. This encrypted token can be used to access other sites sharing the same top level domain. It is assumed that each user has a single account, which is used to access multiple sites. If you are not familiar with JWT, I highly recommend reading “The Anatomy of a JSON Web Token.”2
The Authentication Service
The primary goal of the authentication service is to perform the actual authentication of users and to initiate the creation of the token. A user would send an email address and password to a predefined URL, such as https://dzone.com/services/auth, using the POST method over HTTPS. To keep things simple, these credentials would be handled by a servlet’s doPost() method.
@WebServlet("/auth")
public class AuthServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// retrieve the email and password from the request
String email = request.getParameter("email");
String password = request.getParameter("password");
// authenticate user
User user = new User(email, password);
// the authenticate() method code was omitted because it’s domain specific
boolean authenticated = new AuthService().authenticate(user);
if(authenticated) {
String token = null;
TokenService tokenService = new TokenService();
try {
token = tokenService.generateToken(user);
}
catch (NoSuchAlgorithmException | JOSEException | IOException e) {
// TODO handle exceptions
}
// TODO store token and send response
}
else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
}
At this point, the token is ready for storage and the response should have been sent. The details of token storage are discussed next.
Generating an Encrypted JWT
As highlighted in the above code, token generation is handled by the TokenService class. Its implementation is based on the Nimbus JOSE + JWT3 library, which supports both the JSON Web Signature (JWS) and JSON Web Encryption (JWE) standards. To use these two standards, you would need a key to sign and encrypt the token. The code snippet below shows you how to generate a secret key using the KeyGenerator4 class from javax.crypto package. Once the key is generated, its primary encoding format is obtained. This format is needed for signing and encryption of the token.
// generate 256-bit AES key
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(256);
SecretKey key = generator.generateKey();
// get the key in encoded format
byte[] encodedKey = key.getEncoded();
Now that the key is ready, it is time to create the token. This can be done by defining a set of claims that form the payload of the token.
public class TokenService {
private static final long HOUR = 3600 * 1000;
public String generateToken(User user) throws NoSuchAlgorithmException, JOSEException, IOException{
String encryptedToken = null;
// Prepare JWT with claims set
Date issueDate = new Date();
Date expireDate = new Date(issueDate.getTime() + 12 * HOUR);
JWTClaimsSet claims = new JWTClaimsSet();
claims.setSubject("authentication token");
claims.setIssueTime(issueDate);
claims.setExpirationTime(expireDate);
claims.setIssuer("https://dzone.com");
claims.setCustomClaim("email", user.getEmail());
claims.setCustomClaim("password", user.getPassword());
// the implementation of getEncodedKey() is similar to the code snippet above
byte[] encodedKey = new KeyService().getEncodedKey();
// TODO create a signed and encrypted JWT
return encryptedToken;
}
}
The generateToken() methods starts by defining six claims, four of which are used to verify the validity of the token while the remaining two are application specific. These two custom claims are used to hold the email address and password of the user. This set of claims is used in the following code snippet, which is part of the generateToken() method, to create the signed and encrypted token.
// create a signed and encrypted JWT
// the implementation of getEncodedKey() is shown above as a code snippet
byte[] encodedKey = new KeyService().getEncodedKey();
JWSSigner signer = new MACSigner(encodedKey);
// create a token based on the SHA-256 hash algorithm
SignedJWT signedToken = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claims);
signedToken.sign(signer);
// create JWE header that directly uses 256 bit symmetric key for block encryption (AES)
JWEHeader jweHeader = new JWEHeader.Builder( JWEAlgorithm.DIR, EncryptionMethod.A256GCM).contentType("JWT").build();
// create JWE object with the signed token as the payload
JWEObject jweObject = new JWEObject(jweHeader, new Payload(signedToken));
// encryption the token and serialize it to get its compact form
jweObject.encrypt(new DirectEncrypter(encodedKey));
encryptedToken = jweObject.serialize();
This compact form of the token is used to verify user identity on sites sharing the same top level domain. The next section briefly discusses the approach used to share the token between the sites.
Storage of the Encrypted JWT
As previously mentioned, in the doPost() method under “The Authentication Service” section, the token would need to be stored and sent back as part of the response. This is needed because the client who invoked the authentication service would need the token in future communication with other sites. The approach used here to store the token by using a secure cookie. This cookie would be sent by the browser with each request to any of the sites sharing the same top level domain. Here is the code to create the secure cookie:
// create a secure cookie to hold the token
Cookie cookie = new Cookie("jwt-auth-token", token);
// ensure that the cookie is sent over HTTPS only
cookie.setSecure(true);
// ensure that the cookie cannot be accessed from JavaScript
cookie.setHttpOnly(true);
// a negative value means that the cookie is not stored persistently and will be deleted when the Web browser exits
cookie.setMaxAge(-1);
// the domain name within which this cookie is visible; form is according to RFC 2109
cookie.setDomain(".dzone.com");
// make the cookie visible to all pages
cookie.setPath("/");
// add the cookie to the response and return from the doPost() method
response.addCookie(getCookie(token));
response.setStatus(HttpServletResponse.SC_CREATED);
return;
What is Next?
In the next entry of the series, you will learn how to retrieve the token, verify its content, and access the claim set it holds.
References
- JSON Web Token (JWT) RFC 7519 https://tools.ietf.org/html/rfc7519
- The Anatomy of a JSON Web Token https://scotch.io/tutorials/the-anatomy-of-a-json-web-token
- Nimbus JOSE + JWT library http://connect2id.com/products/nimbus-jose-jwt
- KeyGenerator API http://docs.oracle.com/javase/7/docs/api/javax/crypto/KeyGenerator.html
Opinions expressed by DZone contributors are their own.
Comments