I am building RESTfull backend with ASP.NET Web API 2. FTM I have three clients: AngularJs web application, Android, and IPhone app.
I need to build login via Google and Facebook for all of them and need your help to design it.
My idea is the following (using Google auth as an example):
in Google Developer Console I create 4 "OAuth 2.0 client IDs":
every client will contact Google with its own libraries/apis to authorize and request User consent on the scopes needed but also for offline use. So that authorization_code is received by the client from Google.
every client will call web backend GET method ExternalLogin and send authorization_code as an input parameter.
web backend will exchange (via Google API) re authorization_code to the access_token and receive all needed User information from Google (via Google API). Check the database if User already registered (if not - app will create an account) and then issue local access token that should be used for all the further calls to web backend.
The benefits are pretty straightforward:
My questions are:
The flow is pretty much ok, with one exception: you need to setup client IDs only for the actual client applications (hence the Authorized JavaScript origins
), and not for the backend, too.
Facebook is pretty much the same on the backend side at least (just a different library for validation), you should have no problems abstracting away a generic provider interface.
As for validating the integrity of the ID token on the server side - do a GET on Google's endpoint to verify this token, let themworry about the validation algorithm:
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}
Here's how this could look:
private const string GoogleApiTokenInfoUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}";
public ProviderUserDetails GetUserDetails(string providerToken)
{
var httpClient = new MonitoredHttpClient();
var requestUri = new Uri(string.Format(GoogleApiTokenInfoUrl, providerToken));
HttpResponseMessage httpResponseMessage;
try
{
httpResponseMessage = httpClient.GetAsync(requestUri).Result;
}
catch (Exception ex)
{
return null;
}
if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
{
return null;
}
var response = httpResponseMessage.Content.ReadAsStringAsync().Result;
var googleApiTokenInfo = JsonConvert.DeserializeObject<GoogleApiTokenInfo>(response);
if (!SupportedClientsIds.Contains(googleApiTokenInfo.aud))
{
Log.WarnFormat("Google API Token Info aud field ({0}) not containing the required client id", googleApiTokenInfo.aud);
return null;
}
return new ProviderUserDetails
{
Email = googleApiTokenInfo.email,
FirstName = googleApiTokenInfo.given_name,
LastName = googleApiTokenInfo.family_name,
Locale = googleApiTokenInfo.locale,
Name = googleApiTokenInfo.name,
ProviderUserId = googleApiTokenInfo.sub
};
}