In this blog post we will implement Token-base authentication and will learn how to use Access Token we have created in a previous blog post to communicate with Web Service endpoints which require user to be a registered user with our mobile application.
If you have not followed the previous two blog posts then it is important to have a quick look at them first because this is where we have learned how to:
- Create RESTful Web Service to save a new user profile details in our database and generate user secure password and salt value,
- Create RESTful Web Service to perform user authentication when they profile their userName and password. This is where we generate a secure access token to be used in Token-based authentication to access protected web service endpoints.
And finally below is the last piece where we learn how to use the Access Token to authenticate user and let them communicate with a protected or a secure web service endpoints.
For a free video tutorial on how to build a user interface with Xcode and Swift to implement User Sign up, Sign in, Sign out and learn how to send HTTP Request to a Restful Web Service created in this tutorial check out this Youtube playlist.
HTTP Post Request Containing Access Token
To access protected web service endpoints our mobile application will need to send an HTTP request to a protected web service endpoint and in that HTTP request mobile application will need to include the Authorization Header information and the access token itself. Unless access token is included in HTTP Request, token-based authentication cannot be performed and mobile application will get back a HTTP Status code 401 which means – Unauthorized.
Below is the HTTP GET request example my mobile application can send which demonstrates the use of Authorization header and the token.
curl -X GET http://localhost:8080/api/users/6yGuaKhPFu0MhxNYc2dnFshZQwrWYG -H 'Authorization: Bearer Lx4sbCTfQ91bnSuUzeB64='
Then above example is very simple and is send from the terminal window on my MacBook Pro. Let me break down it a little bit to mention some of its important details:
- Please note how the public user id is used. It is passed as a @PathParam: api/users/6yGuaKhPFu0MhxNYc2dnFshZQwrWYG
- Please note how the access token is passed. It is included in HTTP request as a Authorization header: Authorization: Bearer Lx4sbCTfQ91bnSuUzeB64=. The value of the access token itself, which is Lx4sbCTfQ91bnSuUzeB64= is being stored and read from a KeyChain if we are our app is on iOS.
Web Service Endpoint
To accept this HTTP Get request and to perform token-based authentication and eventually to return return requested information we need to create the below web service endpoint:
@GET @Secured @Path("/{id}") @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) public UserProfileResponse getUserProfile(@PathParam("id") String userId) { UserProfileResponse userProfileResponse = null; UserProfileDto storedUserDetails = usersService.getUserProfileByPublicId(userId); if (storedUserDetails != null) { userProfileResponse = new UserProfileResponse(); BeanUtils.copyProperties(storedUserDetails, userProfileResponse); } return userProfileResponse; }
Please note the use of @Secure annotation and I will paste a code example of how this annotation is created a little bit later in this blog post. But once this annotation is created, every web service endpoint that requires authorization can use this @Secure annotation to perform token-based authentication.
When a request comes to a web service endpoint annotated with @Secure annotation then, first it is an associated filter that will be triggered and executed and only after the logic in associated filter completes, the logic you have in a method annotated with @Secure will executed. So in my example, when http request arrives it is the AuthenticationFilter which will be called first, then if token is successfully validated, the code in the above mentioned getUserProfile(@PathParam(“id”) String userId) method will be called. But if token appears to be invalid, then an exception will be thrown and UserProfile details will not be returned.
Using JAX-RS @NameBinding to Create a @Secure Annotation
To create a @Secure annotation I used a special JAX-RS @NameBinding annotation which allows us to assigned a specific filter or an interceptor to a Resource or a Method. And in my case I have used @NameBinding to bind the above mentioned getUserProfile(@PathParam(“id”) String userId) method to a filter(the source code is a little bit below) with a @Secure annotation.
package com.appsdeveloperblog.ws.utils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; import javax.ws.rs.NameBinding; @NameBinding @Retention(RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Secured { }
AuthenticationFilter to Perform Token-based Authentication
Below is a source code of a AuthenticationFilter which I have created to extract access token from request header, extract user public id and validate the token. Please note that there are different ways to generate access token in the first place and the way I did it might be different from the way you have read in the book or in other blog post. It could be much simpler like a random alpha-numeric string of characters which is not encrypted and does not contain any user specific user information or it could be even more complex and encrypted with a triple-length 3DES keys which are stored outside of your web application, rotated and you might also want to implement access token expiration. So, feel free to change the way I generate the access token if you like.
package com.appsdeveloperblog.ws.utils; import com.appsdeveloperblog.ws.service.UsersService; import com.appsdeveloperblog.ws.service.impl.AuthenticationServiceImpl; import com.appsdeveloperblog.ws.shared.dto.UserProfileDto; import com.appsdeveloperblog.ws.shared.exceptions.AuthenticationException; import java.io.IOException; import java.security.spec.InvalidKeySpecException; import java.util.Base64; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Priority; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.Priorities; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.stereotype.Component; /** * * @author skargopolov */ @Component @Secured @Provider @Priority(Priorities.AUTHENTICATION) public class AuthenticationFilter implements ContainerRequestFilter { @Autowired UsersService usersService; @Autowired AuthenticationUtil authenticationUtil; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // Extract Authorization header details String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer")) { throw new NotAuthorizedException("Authorization header must be provided"); } // Extract the token String token = authorizationHeader.substring("Bearer".length()).trim(); // Extract user id String userId = requestContext.getUriInfo().getPathParameters().getFirst("id"); try { // Validate the token validateToken(token, userId); } catch (AuthenticationServiceException ex) { Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex); requestContext.abortWith( Response.status(Response.Status.UNAUTHORIZED).build()); } } private void validateToken(String token, String userId) throws AuthenticationServiceException { // Get user profile details UserProfileDto userProfile = usersService.getUserProfileByPublicId(userId); String completeToken = userProfile.getToken() + token; String securePassword = userProfile.getUserPassword(); String salt = userProfile.getSalt(); String accessTokenMaterial = userId + salt; byte[] encryptedAccessToken = null; try { encryptedAccessToken = authenticationUtil.encrypt(securePassword, accessTokenMaterial); } catch (InvalidKeySpecException ex) { Logger.getLogger(AuthenticationServiceImpl.class.getName()).log(Level.SEVERE, null, ex); throw new AuthenticationException("Faled to issue secure access token"); } String encryptedAccessTokenBase64Encoded = Base64.getEncoder().encodeToString(encryptedAccessToken); if (!encryptedAccessTokenBase64Encoded.equalsIgnoreCase(completeToken)) { throw new AuthenticationServiceException("Authorization token did not match"); } } }
I hope this example was helpful to you! I will continue to improve it and will put together other pieces like the user interface for the actual user login and the registration page done with the new Xcode and Swift but if you can’t wait then below are a few examples done with earlier versions of Xcode and Swift.
- User login and register/sign up example using Swift on iOS. Video #1
- User login and Register/Sign up example using Swift on iOS. Video #2
- Send HTTP POST Request example using Swift and PHP
- HTTP GET Request Example in Swift
Stay tuned! And if you like to be notified when a new video tutorial or a blog post gets published please subscribe to my blog.
Happy coding!
Sergey