precisely / curious2

0 stars 0 forks source link

Link to 23andMe via OAuth #124

Closed syntheticzero closed 10 years ago

syntheticzero commented 10 years ago

Link to 23andMe via OAuth, download genetic data and store for now.

cc @visheshd

sagrawal31 commented 10 years ago

Implementation Notes

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.

Configuring plugin

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,

  1. twenty3AndMe is the provider name you will use within your application.
  2. api is the scribe provider class which relates to the oauth service you are trying to connect to, and is a subclass of OauthService. It can be anything you want. You will need to import this into your Config.groovy file using the standard java import keyword.
  3. key is the oauth-key you have been given by your provider and secret is the oauth-secret you have been given by your provider.
  4. callback is the uri which is passed while authenticating with any provider and redirects user to this url after completing first step in authentication i.e. getting request token.
  5. successUri is the uri where oauth plugin redirects user after successful authentication,
  6. failureUri is the uri where oauth plugin will redirects user when any case of failure occurs.

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.

Authenticating client

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.

Redirecting user after auth complete

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.

Make API calls after authentication

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.Resource(accessToken, url).

To read more, kindly click here.

Storing genomes data model

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.

visheshd commented 10 years ago

@syntheticzero Ready to be reviewed Currently we are importing only genome data

Other available data endpoints that we could store

Genetics

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

Ancestry

GET /haplogroups GET /ancestry GET /neanderthal GET, PATCH /relatives

Health

GET /risks GET /carriers GET /drug_responses GET /traits

sagrawal31 commented 10 years ago

Kindly change callback url for fitbit & withings production app:

FitBit callback url will be: /oauth/fitbit/callback Withings callback url will be: /authentication/withingCallback. Not required.

syntheticzero commented 10 years ago

I don't quite understand the comment "kindly change callback url" --- am I supposed to do something here?

visheshd commented 10 years ago

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.

syntheticzero commented 10 years ago

Merged to master.