DropWizard is an amazing tool for quickly creating deployable web applications. AngularJS is “HTML enhanced for web apps!” Endeavoring to put them together is pretty easy, but I recently ran into one more complex piece of functionality — user authentication.
DropWizard ships with authentication options, the first of which I’ll describe implementing, Basic Authentication. Put simply, the protocol demands passing user authentication through the request headers via this param:
Authentication: Basic <base64Encoded(username:password)>
AngularJS Setup
Logging in
The first step is creating a form for users to enter their login information and submit it through a Controller
. In this method, collect the username and password, then use the $http
POST to send to the server. The response can contain anything from a boolean or metadata about the user to store in session.
The second step is to take that header and store it as a cookie for the session. The following demonstrates storing and loading for the session, and the login/logout methods.
app.service("UserService", [ "$cookieStore", "$http", function ($cookieStore, $http) { this.user = {}; this.storeToSession = function () { $cookieStore.put("userHeader", this.user.header); }; this.loadFromSession = function () { var userHeader = $cookieStore.get("userHeader"); if (userHeader) { this.loadCurrentUser(userHeader); } }; this.loadCurrentUser = function (loadCurrentUser) { /* $http get to load user info with given header */ }; this.login = function (email, password, ctrl) { var userIn = { username: username, password: password, }; var that = this; $http .post("./login", userIn) .success(function (data) { if (data.success === true) { that.user.username = userIn.username; that.user.header = btoa(userIn.username + ":" + userIn.password); ctrl.errorMessage = ""; that.storeToSession(); } else { /* show error and logout user */ } }) .error(function (arg) { /* show error and logout user */ }); }; this.logout = function () { this.user = {}; this.storeToSession(); }; this.loadFromSession(); }, ]);
Requests
Lastly, include the authorization in $http
requests:
$http.get(<url>,{headers: {'Authorization': 'Basic ' + UserService.user.header}}) // ...
DropWizard Setup
Application Configuration
Following DropWizard’s tutorial is 90% of the work. Follow it to make your SimpleAuthenticator. I also created a pojo for a User with the username and password fields which I added to my controller methods.
Next comes updating your Application
to include the Authenticator
. I placed a static CachingAuthenticator in my Application which I could reference from my resources to authenticate from my controller. Note too I’m using the AuthenticationCachePolicy described in DropWizard’s manual to control TTL:
SimpleAuthenticator authenticator = new SimpleAuthenticator(); cachedAuthenticator = new CachingAuthenticator<BasicCredentials, User>(new MetricRegistry(), authenticator, config.getAuthenticationCachePolicy()); environment.jersey().register(new BasicAuthProvider<User>(cachedAuthenticator, "REALM MESSAGE"));
Controllers
The driving force is the @Auth
annotation, which will pull the authorization from the header and construct the associated object:
@GET @Path("/valid") public String valid(@Auth User user) throws AuthenticationException { log.info("valid user: " + user); return SUCCESS_JSON; }
In this example, my User is injected with the given values, and must be authenticated by my existing Authenticator
implementation.
By default, if the authorization isn’t present in the header and the user attempts to access an endpoint which requires it, a browser-specific dialog will appear, showing the realm message. Since we have the login form written in AngularJS, we need a controller method to make the authentication:
@POST @Path("/login") @Consumes(MediaType.APPLICATION_JSON) public UserAuthResponse login(User user) throws AuthenticationException { Optional<User> res = DropwizardApplication.getCachedAuthenticator() .authenticate(user.toCredentials()); if ( res.isPresent() ) { return new UserAuthResponse(res.get()); } else { throw new MessageWrappedException("Unable to log in with those credentials!"); } }
And now we have:
- An authentication service attached to the jersey environment
- The opportunity to expand authentication logic in the
Authenticator
implementation - A RESTful API for authenticating and doing secure transactions
- A login form which can submit to this method
Next Steps
One immediate update I did was to wrap all $http
requests in a service which will automatically include the header information. This way, only one service has to manage the security and responses for invalid or missing authentication.
Future steps include migrating away from Basic Authentication to another, like OAuth. Also, a per pageload check can be added to confirm the user’s session is active, pulling the user information each time or caching it in a cookie. Using the CachedAuthenticator, a response can be used for the TTL of the authentication.
Thank you for every other excellent post. Where else may anyone get that type
of info in such a perfect manner of writing? I’ve a presentation subsequent week,
and I’m on the search for such info.
Take a look at my web-site … bing.com (Denese)
Can you please elaborate with a detailed example. I am new to Angular JS?
Regards
Ram
I know this is a pretty old post, but could you leave a link to the sourcecode for this?
thank you, this is very helpful
Great guide, thank you! Works great except for one thing:
I tried the CachingAuthenticator to achieve TTL functionality but after leaving the server and angular app running over night I was still able to reach and execute my rest services even though I have set authenticationCachePolicy: maximumSize=10000, expireAfterAccess=10m This led me to check the implementation of CashingAuthenticator and it looks to me the expireAfterSucess just evicts the credentials from the cache, thus keeping it clean and up to date. If the user tries to autheticate again it will generate a cache miss and just use the underlying authenticator wich will authenticate again. So I guess I need to have some functionality in the angular UserService that invalidates the user after a given amount of time.
Or am I missing something