Closed syntheticzero closed 10 years ago
Using oauth plugin to make authentication with 23andme api.
This plugin provides better & efficient way to make api calls to any website, and also enables us to consume services from third-party providers which are protected by oauth based security.
The term provider used all over the application refers to any third-party website from which we needs to use the services.
To configure a provider we need to configure our Config.groovy in such a way:
import somepackage.Twenty3AndMeApi
...
oauth {
providers {
twenty3AndMe { // All lower case, will be available as twenty3andme
api = Twenty3AndMeApi // Explained below
key = 'api-key'
secret = 'api-secret'
callback = '${grails.serverURL }/oauth/twenty3andme/callback'
successUri = '/your/success/page'
failureUri = '/your/failure/page'
}
}
}
Here,
By default scribe provides many providers api class. Click here to view all. But we can implement our own api.
To implement our own provider, simply subclass either DefaultApi10a or DefaultApi20 depending on the implementation of oauth your provider supports, and provide your implementation class to the configuration's api parameter.
So for 23andme, we've implemented our own api, Twenty3AndMeApi which extends DefaultApi20 because 23andme uses oauth2.
To authenticate out client with any provider we need to redirect them at certain url: /oauth/$provider/authenticate
. For example: /oauth/facebook/authenticate
or /oauth/twenty3andme/authenticate
.
The rest of the authentication is done by oauth plugin & client will be redirected to successUri.
Since we may require to use protected resource any where in our code which requires access token for the client. For example at certain point in our any dataservice, we need to authenticate the user with 23andme api, for this we need to tell our calling action to redirect client (user) to /oauth/23andme/authenticate
& that action will redirect our client to the url for authentication. To avoid this overhead & make execution more efficient, we've implemented a custom exception named AuthenticationRequiredException.
Throwing this exception from any where with provider name as message of the exception will automatically redirects user to authentication page. Click here for example.
This uses the feature of grails url mapping. See Declarative Error Handling section in grails documentation.
As described above, oauth plugin redirects user (client) to one of the successUri & failureUri depending upon if authentication is successful or not. So this approach is not applicable where we needs to redirect user to different url's as per the need.
To overcome this problem, we've defined a AuthenticationController which have some actions based on pattern <providerName>Auth
. Now successUri & failureUri of each provider configuration can be reconfigured to something like this:
oauth {
providers {
twenty3andme {
api = Twenty3AndMeApi
key = "dummy-key"
secret = "dummy-secret"
callback = "${grails.serverURL }oauth/twenty3andme/callback"
successUri = "authentication/twenty3andme/success"
failureUri = "authentication/twenty3andme/fail"
scope = "names basic genomes"
}
}
}
Url mapping defined here will automatically redirects user to actions defined in authentication controller based on provider name, where they can be redirected to different url's stored in a session variable named returnURIWithToken.
Now the point is that, where to set this returnURIWithToken session variable. Throwing AuthenticationRequiedException will automatically do this for you. The action will automatically creates this variable with exact url with all parameters which was entered while exception is thrown. To see how this works, please click here.
After successful authentication with the thirdparty, the user's access token is stored in the session. To use that token from session, we can write:
Token tokenInstance = session[oauthService.findSessionKeyForAccessToken(providerName)]
Example:
Token tokenInstance = session[oauthService.findSessionKeyForAccessToken("twenty3andme")]
Now to access any secure url, oauth provider a better implementation: Suppose to retrieve current user's profile, we need to hit https://api.23andme.com/1/names/ with get request, writing this:
Response apiResponse = oauthService.getTwenty3AndMeResource(tokenInstance, "https://api.23andme.com/1/names/")
The convention used for accessing resources is oauthService.
To read more, kindly click here.
Every 23andme account can have multiple genotyped account profile. For example:
{
"id": "a42e94634e3f7683",
"profiles": [
{
"genotyped": true,
"id": "c4480ba411939067"
}, ...
]
}
So after every authentication, an instance of OAuthAccount is created if not present with the accountId set to top most profile id shown in above example.
Now to store genome data of each profile an instance of Twenty3AndMeData is created with having link to above said instance of OAuthAccount, profileId of each profiles (as shown in example, here only one profile), and data as received from api.
@syntheticzero Ready to be reviewed Currently we are importing only genome data
Other available data endpoints that we could store
GET /genotypes -- This accepts another parameter called locations and returns genotypes based on that. Not sure what to send as values for these or what locations we care to store
GET /haplogroups GET /ancestry GET /neanderthal GET, PATCH /relatives
GET /risks GET /carriers GET /drug_responses GET /traits
Kindly change callback url for fitbit & withings production app:
FitBit callback url will be:
I don't quite understand the comment "kindly change callback url" --- am I supposed to do something here?
Sorry that was for me. Had to add the callback url to the app on dev.fitbit.com. Done. This can be merged now. Will have to test it on production once deployed.
Merged to master.
Link to 23andMe via OAuth, download genetic data and store for now.
cc @visheshd