Hi All,
I just pushed into master what is going to become version 3 of SecureSocial. These changes are meant to fix the pain points that many are experiencing. It’s still a draft but I think it’s good enough to share. Due to the nature of the changes it was impossible to maintain backwards compatibility but I think upgrading will be worth the effort.
Here’s a summary of the main changes:
1) Async support
All the module should be async now. I refactored all the places where blocking calls were being made.
2) Managed Controllers
All controllers are no longer objects but rather traits that you can extend. You will see all of them come in pairs, e.g.: BaseLoginPage and LoginPage. The first is a trait that implements the functionality and the second a class that implements it. The implementing class is then referenced from the module routes file.
3) Removed RoutesHelper
Having managed controllers allowed me to remove the RoutesHelper that was causing compilation problems when custom routes were used. Now you can just add/remove routes to enable/disable the functionality you need and you should not get compilation errors. There is now a RoutesService that needs to be customized for some pages when you do not use the default routes.
4) Routes file
The module has a default routes file that you can include in your app using something like:
# your app routes
GET / myController.index
-> auth securesocial.Routes
That will include all the routes, if you want a subset of the routes or change the paths just add the routes manually.
5) Dependency Injection support
Since all controllers are managed you can now extend them and add any dependency either in the code itself or by using your preferred DI framework.
6) Plugins
Using plugins was causing some problems and I decided to make them regular classes.
7) Services
I turned some helper classes and objects into services: a trait defines the interface and a default implementation is provided with the original code. For example things like:
- HttpService that allows you to make http calls. The default implementation uses the WS class from Play.
- CacheService: The default implementation uses the Play cache
- AvatarService: A trait to get avatars from external services. The default implementation is using Gravatar.
8) RuntimeEnvironment
I added the concept of a RuntimeEnvironment. This class holds references to all the services and classes the module needs to work properly. Think of it as a replacement of the play.plugins file.
A nice side effect of having this is that you can define different environments for parts of your app. Eg: an admin module can use a its own UserService or a different set of providers.
Each controller needs an instance of this class so it is now required to implement the getControllerInstance in Global. The sample apps have a basic code that check if a controller that is about to be instantiated needs it and invoke the constructor with the sample RuntimeEnvironment implementation. You can decide how to pass the environment, either using reflection as in the samples, using a DI framework or extending the controller and overriding the env attribute and hardcoding a reference to your environment.
9) Renamed Identity to BasicProfile
A BasicProfile is the same set of data that the Identity trait used to hold. The module creates a BasicProfile when an authentication is successful and passes the instance to your app, you can then return an instance of your own object and use it in the actions.
All the object copying that was using the SocialUser class is gone. I refactored the providers a bit so that is not needed anymore.
10) Custom user model support
The module allows you to use your own class for the user object now. There’s no need to implement a trait or extend anything. So if you had something like:
def myAction = SecuredAction { implicit request =>
request.user match {
case u: MyUser => // do something
case _=> // handle error
}
}
You can just simplify it to using request.user and forget about the ugly match.
Using a custom user model is optional, the idea is that if a BasicProfile is good enough for your app you can just use that. The default controllers (not their base traits) use a RuntimeEnvironment that handles BasicProfile, e.g.: RuntimeEnvironment[BasicProfile].
11) Authenticator, AuthenticatorBuilder and AuthenticatorService
Authenticators can now be extended and customized. The module comes with two implementations: CookieBasedAuthenticator and HttpHeaderAuthenticator. In theory you can now create an authenticator that stores anything on the client side. This can be used to serialize all the user information so that invocations to SecuredActions do not need to look up the user in the database.
The new implementation lets you use different Authenticator implementations to protect the actions, this means the same action can be invoked by a client using cookies and an http header.
12) SecuredAction
Some pointed out the need to use content negotiation in their secured apps instead of the ajaxCall flag so I decided to remove it.
Also, the not authenticated and not authorized responses can be overridden (in the Scala API). I had issues trying to accomplish the same on the Java API (in fact, I bumped into several problems with Java) that I’ll try to fix, the Java side works but it needs some improvements.
13) Testing
Having the services layer should make it easier to test apps as the services can be mocked more easily. Eg: mocking an authenticator to just return a user.
14) Single Page Apps and Mobile support
Having the HttpHeader based authenticator allows for easier integration with SPA and mobile apps. I added a LoginApi controller that lets you authenticate a user using an API. It supports the UsernamePasswordProvider and all the OAuth2Providers.
In the case of the UsernamePasswordProvider you can post the user credentials and if they’re ok you will get a json with a token that can be used in an X-Auth-Token header to invoke SecuredActions. For example:
For OAuth2 based providers you have to post a JSON with an accessToken generated by the external service (that was obtainer in the client side) along with the user email. The module will the use the accessToken to verify if it works and will compare the email returned by the external service to the one passed in. If they match then the user is considered to be authenticated. This is very similar to what the guys at FortyTwo were doing and I thought it would be good to have the functionality built in (
http://eng.42go.com/mobile-auth-with-play-and-securesocial/).
For example, having a file test.json with the accessToken and expiresIn values returned after authenticating with Facebook on the client side (e.g.: using Javascript):
{
"info": {
"accessToken": “an_access_token”,
"expiresIn": a_number_with_expiration_in_seconds
}
}
You can invoke:
A sample json response for any of the calls above would be:
{"token":"98b9613dac60890b8e0abf5bc0f77591523df4e6de50b085c832116b8db2cc65511e0de6780f6a49f8755eddabbd46e6afada92160758fd6d4bbb25dc57e0f7b1e4b5b59fbbe543cf80ad1b6d91de7764e3ac1aaa0afac0c312a47bf27258f455606c6c19b1a3d40f8631ce98e6b76e128dddcb29511eb81200ffe9de95cba7a","expiresOn":"2014-05-07T07:43:10.987-03:00"}
You can then invoke a secured action as:
curl -v --header "Content-Type: application/json" -H "X-Auth-Token: 819a9cb9227d2c82af9c1ee2a62b9e7d35725e235e086ab95ecce0b509f3f7b389f430e217e341306ecaebfd1972ac083de73a32341a26f97150ae71fb0417f0031534d818356b2266ffc100e5ee6a50bd1f9ec76b0f68d2ff8ce4d196b4a86b61e002b29b00532ef166cb2eb8476d3ae008c112891628bc0f444c7512c01345"
http://localhost:9000/my-protected-action
There are a lot of other minor changes but this covers the main points. The documentation has not been updated yet, so use the sample apps as a guide and take a look at the source code :) I wanted to share what I have so far to get feedback and adjust things in preparation for a version 3.
Thanks to all of you who sent their feedback - either positive or negative - since it influenced and helped improve SecureSocial.
Let me know what you think,
Jorge