Implementing Social login in Android without using heavy SDKs

Hamza El Yousfi
14 min readJan 22, 2019

--

Every good app needs good UI/UX. Having a good user experience means make it easy and fast for people to get into and around your app. If your app needs to authenticate users, either to send data to your backend or to customize it accordingly to the person using it, you might find yourself creating some big architecture behind the scene to accommodate that. It takes time and can also be frustrating for the user as they might not feel like getting though the whole process of registration -> confirmation -> activation.

Hopefully there is an alternative for doing this. We can ask the users to authenticate using their social media accounts, this will guarantee that the user has been already verified and most probably it’s a legit user.

In this tutorial I will be covering how to achieve this authentication using 3 of the most popular social medias i.e: Google, Facebook and Twitter.

> NB: There is a different way of doing this authentication, you can use the AccountManager Android class, this will allow you to authenticate the users with apps that they already have on their phone. However if the users don’t have, for example Facebook App on then you will not be able to authenticate them using Facebook

Summary

1-Prerequisites

2-OAuth2 with Google

3-OAuth2 with Facebook

4-OAuth1.0a with Twitter

Bonus: Web Page to achieve successful redirect

How does it work?

Most of the providers use OAuth mechanism for their authentication. In our case, we will use both versions of OAuth. Google and Facebook uses OAuth2, however Twitter uses OAuth1.0a. So after this tutorial you will be able to implement OAuth for other providers as needed.

The main goal at the end is to know who is using our app, be it OAuth2 or OAuth1. To do so, we will need a flow similar to the following:

1- We tell the provider who we are (our app id) so they know that we are legit and they send us back a grant

2- We use the grant that we just got and ask the user to authenticate himself within the provider using whatever method he wants (this happens at the provider’s side). If the authentication is successful, the provider send us back an Access Token to define the current user.

3- We use the Token as we please to receive/send information for that user

Prerequisites

I have created a ready to clone repos for the purpose of this tutorial. You can find it here : Android Social Login
To be able to communicate with the Authentication providers we could use any HTTP way, however there is a very handy library available, that makes the whole OAuth process simpler, openid/AppAuth. Make sure to check their repo if you have any questions.

In this tutorial I will be using Android Studio 3.3 and Kotlin as a language.

So first create your project if you don’t already have one then add:

  • AppAuth for handling the OAuth processes
  • Okio for processing files. It will be used to detect changes in configuration and invalidate the Authentication when necessary
  • rxkotlin and rxandroid for reacting to events happening during the process
dependencies {
implementation 'net.openid:appauth:0.7.1'
implementation 'com.squareup.okio:okio:2.2.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}

After adding the dependencies sync your project. If you get the error :

Manifest merger failed : Attribute data@scheme at AndroidManifest.xml requires a placeholder substitution but no value for <appAuthRedirectScheme> is provided.`

Just add this into your app build.gradle file in order to be able to build your project (we will come back to it shortly)

defaultConfig{
manifestPlaceholders = [
'appAuthRedirectScheme': ''
]
}

Our main layout will consist of 3 Buttons for the different OAuth providers and a TextView displaying our Access Token, when we get one.

Setting up AppAuth to meet our needs

The guys from AppAuth have provided a nice Demo App for their library. The issue is that only Google, Okta?? and Gluu?? are documented there. This tutorial takes few of the helper Classes provided by the demo app and customize them to be able to handle more than one provider at once.

The Authentication State Manager

This singleton will be responsible for handling the state of our users. It will help us read/write to our Authentication holder (in this example it’s the Shared Preferences file) whenever there is a state change. The app will rely on the AuthStateManager to know for example if the token has expired, where to send the authentication request and how to send it.

package org.mayday.sociallogins

import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.AnyThread
import net.openid.appauth.*
import org.json.JSONException
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.locks.ReentrantLock


class AuthStateManager(context: Context) {

private val mPrefs: SharedPreferences
private val mPrefsLock: ReentrantLock
private val mCurrentAuthState: AtomicReference<AuthState>

val current: AuthState
@AnyThread
get() {
if (mCurrentAuthState.get() != null) {
return mCurrentAuthState.get()
}

val state = readState()
return if (mCurrentAuthState.compareAndSet(null, state)) {
state
} else {
mCurrentAuthState.get()
}
}

init {
mPrefs = context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE)
mPrefsLock = ReentrantLock()
mCurrentAuthState = AtomicReference()
}

@AnyThread
fun replace(state: AuthState): AuthState {
writeState(state)
mCurrentAuthState.set(state)
return state
}

@AnyThread
fun updateAfterAuthorization(response: AuthorizationResponse?, ex: AuthorizationException?): AuthState {
val current = current
current.update(response, ex)
return replace(current)
}

@AnyThread
fun updateAfterTokenResponse(response: TokenResponse?, ex: AuthorizationException?): AuthState {
val current = current
current.update(response, ex)
return replace(current)
}

@AnyThread
fun resetState() : AuthState {
val clearedState = AuthState()
return replace(clearedState)
}

@AnyThread
fun updateAfterRegistration(response: RegistrationResponse?, ex: AuthorizationException?): AuthState {
val current = current
if (ex != null) {
return current
}

current.update(response)
return replace(current)
}

@AnyThread
private fun readState(): AuthState {
mPrefsLock.lock()
try {
val currentState = mPrefs.getString(KEY_STATE, null) ?: return AuthState()

try {
return AuthState.jsonDeserialize(currentState)
} catch (ex: JSONException) {
return AuthState()
}

} finally {
mPrefsLock.unlock()
}
}

@AnyThread
private fun writeState(state: AuthState?) {
mPrefsLock.lock()
try {
val editor = mPrefs.edit()
if (state == null) {
editor.remove(KEY_STATE)
} else {
editor.putString(KEY_STATE, state.jsonSerializeString())
}

if (!editor.commit()) {
throw IllegalStateException("Failed to write state to shared prefs")
}
} finally {
mPrefsLock.unlock()
}
}

companion object {

private val INSTANCE_REF = AtomicReference(WeakReference<AuthStateManager>(null))

private const val TAG = "AuthStateManager"

private const val STORE_NAME = "AuthState"
private const val KEY_STATE = "state"

@AnyThread
fun getInstance(context: Context): AuthStateManager {
var manager = INSTANCE_REF.get().get()
if (manager == null) {
manager = AuthStateManager(context.applicationContext)
INSTANCE_REF.set(WeakReference(manager))
}

return manager
}
}
}

The Configuration utility reader

As the title suggests, this Singleton will hold an instance of our Authentication configuration. It will also load the appropriate config file depending on what the user selects. One handy feature of this helper is checking automatically if the App is well configured for handling OAuth redirect.
As I mentioned earlier, the users will have to authenticate themselves on the provider’s site but then will need to come back to your app automatically in order to complete what they were doing.
This is where the placeholder we left empty earlier in the gradle.build file comes in play and I will show you how I used it in order to have the 3 providers working correctly.

// Returns true if there is an Activity able to handle the redirectURI otherwise returns false
val isRedirectUriRegistered: Boolean
get() {
val redirectIntent = Intent()
redirectIntent.setPackage(mContext.packageName)
redirectIntent.action = Intent.ACTION_VIEW
redirectIntent.addCategory(Intent.CATEGORY_BROWSABLE)
redirectIntent.data = redirectUri

return !mContext.packageManager.queryIntentActivities(redirectIntent, 0).isEmpty()
}

The rest of the file is just getters for the different properties of the JSON file described below.

{
"client_id": "Id provided by the Authentication Authority",
"redirect_uri": "where to send the user after the auth is done",
"authorization_scope": "what kind of information you need",
"authorization_endpoint_uri": "where do you need to send your request",
"token_endpoint_uri": "where do you need to query the user token",
"https_required": true/false depending on the provider
}

We will setup this file for each of the providers we want to target. From my experience, you have here all the necessary information to successfully authenticate your users in most Social platforms.

OAuth2 with Google

I have to say that the authentication with Google using AppAuth works perfectly out-of-the-box. So I will use this part to describe how the whole process works, then on the remaining providers I will just explain the differences.

Remember you can always have a look at the full code in the repo below

A. Setup Google Account

Before even starting to code, we need to fill the json file that we will use for OAuth2. To do so, you need to create an account on Google Cloud and head to https://console.cloud.google.com/apis/ to create new OAuth client. On the drop down you can use Other if you’re planning on using the same credentials for multiple apps.

Once you get the Client Id, you can insert it in the google.json parameters file:

{
"client_id": "client_id",
"redirect_uri": "client_id:/oauth2redirect",
"authorization_scope": "email",
"authorization_endpoint_uri": "https://accounts.google.com/o/oauth2/auth",
"token_endpoint_uri": "https://accounts.google.com/o/oauth2/token",
"https_required": true
}

B. Authenticate the user

The authentication goes as follow:

1- Init our Executor and AuthStateManager on the onCreate function

2- Listen for the clicks on the Button

3- When user click on login, we re-instantiate our AuthService (to override if there were previous params set)

4- We then load the config for the chosen provider and set it into our AuthStateManager

5- At this point, the AuthStateManager and the client are all setup and ready to contact the provider, so we build our AuthorizationRequest and and prepare our intent

6- We start our ActivityForResult and present the user with the login screen. The user at this point is on his default browser on Google’s site presented with the information about your app and wether or not he would like to give you access to his informations

7- We wait for onActivityResult, and we check if the data coming from the intent has our Authorization Token

8- If it does, it mean the user has granted us access, so now we need to use the Authorization Token to get the user’s Access Token. For OAuth2 this is fully handled by AppAuth, so all we have to do is give all those objects to Auth Service performTokenRequest function and we’ll get our Access Token as result on the callback.

private val mClientId = AtomicReference<String>()
private val mAuthRequest = AtomicReference<AuthorizationRequest>()
private val mAuthIntent = AtomicReference<CustomTabsIntent>()
private var mExecutor: ExecutorService? = null

private var chosenProvider: String = ""

private var authServerObserver = object : Observer<Boolean> {

override fun onSubscribe(d: Disposable) {}

override fun onNext(s: Boolean) {
if(s) startAuth()
}

override fun onError(e: Throwable) {}

override fun onComplete() {}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mExecutor = Executors.newSingleThreadExecutor()
mAuthStateManager = AuthStateManager.getInstance(this)
if (intent.getBooleanExtra(EXTRA_FAILED, false)) {
displayAuthCancelled()
}
}

fun onAuthenticateClickListener(view: View){
chosenProvider = view.tag.toString()
recreateAuthorizationService()
startAuthProcess()
}

private fun recreateAuthorizationService() {
if (mAuthService != null) {
mAuthService!!.dispose()
}
mAuthService = createAuthorizationService()
mAuthRequest.set(null)
mAuthIntent.set(null)
}

private fun createAuthorizationService(): AuthorizationService {
val builder = AppAuthConfiguration.Builder()
builder.setConnectionBuilder(ConnectionBuilderForTesting.INSTANCE)
return AuthorizationService(this, builder.build())
}

private fun startAuthProcess(){
mConfiguration = Configuration.getInstance(this, chosenProvider)
if (!mConfiguration.isValid) {
return
}
mConfiguration.acceptConfiguration()
mExecutor?.submit {
initializeAppAuth()
}
}

@WorkerThread
private fun initializeAppAuth() {
val config = AuthorizationServiceConfiguration(mConfiguration.authEndpointUri!!, mConfiguration.tokenEndpointUri!!)
mAuthStateManager.replace(AuthState(config))
initializeClient()
return

}

@WorkerThread
private fun initializeClient() {
mClientId.set(mConfiguration.clientId)
runOnUiThread { createAuthRequest() }
return
}

private fun createAuthRequest() {
val authRequestBuilder = AuthorizationRequest.Builder(
mAuthStateManager.current.authorizationServiceConfiguration!!,
mClientId.get(),
ResponseTypeValues.CODE,
mConfiguration.redirectUri!!
).setScope(mConfiguration.scope!!)
mAuthRequest.set(authRequestBuilder.build())
authServerObserver.onNext(true)
}

@MainThread
private fun startAuth() {
mExecutor?.submit { doAuth() }
}

@WorkerThread
private fun doAuth() {
val intent = mAuthService?.getAuthorizationRequestIntent(mAuthRequest.get(), mAuthIntent.get())
startActivityForResult(intent, RC_AUTH)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_CANCELED) {
displayAuthCancelled()
} else {
val response = AuthorizationResponse.fromIntent(data!!)
val ex = AuthorizationException.fromIntent(data)
when {
response?.authorizationCode != null -> {
mAuthStateManager.updateAfterAuthorization(response, ex)
exchangeAuthorizationCode(response)
}
ex != null -> return // Authorization flow failed
else -> return // No authorization state retained - reauthorization required
}
}
}

@MainThread
private fun exchangeAuthorizationCode(authorizationResponse: AuthorizationResponse) {
performTokenRequest(
authorizationResponse.createTokenExchangeRequest(),
AuthorizationService.TokenResponseCallback { tokenResponse, authException ->
this.handleCodeExchangeResponse(
tokenResponse,
authException
)
})
}

@MainThread
private fun performTokenRequest(request: TokenRequest, callback: AuthorizationService.TokenResponseCallback) {
val clientAuthentication: ClientAuthentication
try {
clientAuthentication = mAuthStateManager.current.clientAuthentication
} catch (ex: ClientAuthentication.UnsupportedAuthenticationMethod) {
// Token request cannot be made, client authentication for the token endpoint could not be constructed
return
}
mAuthService!!.performTokenRequest(request, clientAuthentication, callback)
}

@WorkerThread
private fun handleCodeExchangeResponse(tokenResponse: TokenResponse?, authException: AuthorizationException?) {
if(authException != null){
// Log the error
return
}
accessTokenTextView.text = tokenResponse!!.accessToken!!
}

private fun displayAuthCancelled() {
Toast.makeText(this, "Authorization canceled", Toast.LENGTH_SHORT).show()
}

OAuth2 with Facebook

For Facebook the procedure is very similar, the only difference is that while Google only asked us for the Client Id, Facebook asks for both the App id and the App Secret when requesting an Authorization Token. But before we start, we need to setup the account first

A. Setup Facebook App Account

Head to https://developers.facebook.com/ , create a new app then go to Settings->Basics and you will find your App Id and App Secret. We will use them to fill our facebook.json.

NB: From my experience facebook doesn’t accept scheme redirect, so we need to use a Url in here. As a workaround, I use a url that sends to web page and inside this web page I redirect the user to the app using the scheme (more on this in the bonus part)

{
"client_id": "client_id",
"redirect_uri": "https://mywebsite.com/oauth",
"authorization_scope": "email",
"authorization_endpoint_uri": "https://www.facebook.com/dialog/oauth",
"token_endpoint_uri": "https://graph.facebook.com/oauth/access_token",
"https_required": true
}

B. Authenticate the user

Before diving into the Main Activity code, we need to make sure that our app can handle our redirect_uri. Technically we will not use it to go back directly to our App, but Facebook will send us the Access Token back to that URL and from there WE will send the user back to the App. Don’t worry, this all happens in less than a second so the user might not even notice.

Go to your manifest and add this inside your activity:

<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"
android:host="mywebsite.com"
android:path="/oauth"/>
</intent-filter>

Now we head to our Main Activity ti handle the user login process. The steps are exactly the same as the procedure for Google we just need to insert our App Secret before sending the Access Token request.

@MainThread
private fun performTokenRequest(request: TokenRequest, callback: AuthorizationService.TokenResponseCallback) {
val clientAuthentication: ClientAuthentication
try {
clientAuthentication = mAuthStateManager.current.clientAuthentication
} catch (ex: ClientAuthentication.UnsupportedAuthenticationMethod) {
// Token request cannot be made, client authentication for the token endpoint could not be constructed
return
}
val finalRequest : TokenRequest = when (chosenProvider) {
FACEBOOK_TAG -> {
getFinalRequest(request)
}
else -> {
request
}
}

mAuthService!!.performTokenRequest(finalRequest, clientAuthentication, callback)
}

private fun getFinalRequest(request: TokenRequest) : TokenRequest {
val params = HashMap<String, String>()
params["client_secret"] = getString(R.string.facebookApiSecret)
return TokenRequest.Builder(request.configuration, request.clientId)
.setGrantType(request.grantType)
.setAuthorizationCode(request.authorizationCode)
.setRedirectUri(request.redirectUri)
.setCodeVerifier(request.codeVerifier)
.setScope(request.scope)
.setRefreshToken(request.refreshToken)
.setAdditionalParameters(params)
.build()
}

That’s it! As you can see, the function getFinalRequest constructs a new Request and add to it the App Secret.

NB: If you try it like this, you will not get back to the App automatically but you will be able to see the token in your url . From there you can get the Access Token and send it back to the app using the Google scheme i.e : google_client_id:/oauth2redirect?access_token=token_you_got_from_FB

OAuth1.0a with Twitter

Surprisingly, Twitter uses OAuth1 and AppAuth is not really adapted to that, however we will work around it by using Retrofit to get the OAuth Token manually. But I have to tell you, this was bit of a headache.

A. Setup Twitter App Account

To setup a Twitter Account head to https://developer.twitter.com/en/apps. Create a new App, this will give you an App Id and App Secret. To make things easy, I’ve setup the redirect_url to be the same as for facebook, this way we will not have to handle two different redirections.

{
"client_id": "app id",
"redirect_uri": "https://mywebsite.com/oauth",
"authorization_scope": "email",
"authorization_endpoint_uri": "https://api.twitter.com/oauth/authorize",
"token_endpoint_uri": "https://api.twitter.com/oauth/request_token",
"https_required": true
}

B. Authenticate the user

If you checked the Facebook part, you will notice that we already handle the twitter redirect on our manifest (it’s same as the facebook redirect), so no need to change anything there.

Now on the Main Activity we need to setup few helper functions. The OAuth process for Twitter is explained in details here: https://developer.twitter.com/en/docs/basics/authentication/overview/3-legged-oauth

The process requires encoding and generating encrypted headers for the request. Below are the functions needed to get the access token from twitter.

private fun getTwitterRequestToken() {
val oauthHeader = generateOAuthAuthorizationHeader(null)
val retrofit = Retrofit.Builder()
.baseUrl("https://api.twitter.com/")
.build()
retrofit.callbackExecutor()
val service = retrofit.create(AuthenticationService::class.java)
val call = service.getRequestToken(oauthHeader)
call.enqueue( object: Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
val responseBody = response.body()!!.string()
val regex = "oauth_token=(.*)&oauth_token_secret".toRegex()
twitterAccessToken = regex.find(responseBody)!!.value.replace("oauth_token=","").replace("&oauth_token_secret","")
startAuthProcess()
} else {
// There was an error processing the authorization request params
}
}

override fun onFailure(fail: Call<ResponseBody>, t: Throwable) {
Toast.makeText(this@MainActivity,"Network error: We couldn't connect to Twitter, try again.", Toast.LENGTH_SHORT).show()
}
})
}

private fun getTwitterAccessToken(header: String) {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.twitter.com/")
.build()
retrofit.callbackExecutor()
val service = retrofit.create(AuthenticationService::class.java)
val call = service.getAccessToken(header)
call.enqueue( object: Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
val responseBody = response.body()!!.string()
val accessTokenRegex = "oauth_token=(.*)&oauth_token_secret".toRegex()
val tokenSecretRegex = "oauth_token_secret=(.*)&user_id".toRegex()
val accessToken = accessTokenRegex.find(responseBody)!!.value.replace("oauth_token=","").replace("&oauth_token_secret","")
val tokenSecret = tokenSecretRegex.find(responseBody)!!.value.replace("oauth_token_secret=","").replace("&user_id","")
accessTokenTextView.text = "Access Token: $accessToken & Secret : $tokenSecret"
} else {
// There was an error processing the token request params
}
}


override fun onFailure(fail: Call<ResponseBody>, t: Throwable) {
Toast.makeText(this@MainActivity,"Network error: We couldn't connect to Twitter, try again.", Toast.LENGTH_SHORT).show()
}
})
}

private fun generateOAuthAuthorizationHeader(extraParams: HashMap<String,String>?): String {
val timeStamp = System.currentTimeMillis()/1000
val nonce = UUID.randomUUID().toString().replace("-","")
val params = HashMap<String,String>()
params["oauth_callback"] = "https://mywebsite.com/oauth"
params["oauth_consumer_key"] = "TWITTER APP ID"
params["oauth_nonce"] = nonce
params["oauth_signature_method"] = "HMAC-SHA1"
params["oauth_timestamp"] = timeStamp.toString()
params["oauth_version"] = "1.0"
if(extraParams != null){
params.putAll(extraParams)
}

return generateOAuthHeader(params,"https://api.twitter.com/oauth/request_token")
}

private fun generateOAuthHeader(params: HashMap<String,String>, endpoint: String) : String{
val encodedParams = ArrayList<String>()
params.forEach { (key, value) -> encodedParams.add(percentEncode(key)+"="+percentEncode(value)) }
params.toSortedMap()

Collections.sort(encodedParams, String.CASE_INSENSITIVE_ORDER)

val paramsString = encodedParams.joinToString("&")
val baseString = "POST&${percentEncode(endpoint)}&${percentEncode(paramsString)}"
val signingKey = percentEncode("YOUR TWITTER APP SECRET HERE")+"&"

params["oauth_signature"] = calculateRFC2104HMAC(baseString, signingKey)

val encodedHeaderValues= ArrayList<String>()
params.forEach { (key, value) -> encodedHeaderValues.add(percentEncode(key)+"="+"\""+percentEncode(value)+ "\"") }

return "OAuth ${encodedHeaderValues.joinToString(", ")}"
}

private fun percentEncode(s: String?): String {
if (s == null) {
return ""
}
try {
return URLEncoder.encode(s, "UTF-8")
// OAuth encodes some characters differently:
.replace("+", "%20").replace("*", "%2A")
.replace("%7E", "~")
// This could be done faster with more hand-crafted code.
} catch (wow: UnsupportedEncodingException) {
throw RuntimeException(wow.message, wow)
}
}

private fun calculateRFC2104HMAC(data: String, key: String): String {
val signingKey = SecretKeySpec(key.toByteArray(), "HmacSHA1")
val mac = Mac.getInstance("HmacSHA1")
mac.init(signingKey)
return android.util.Base64.encodeToString(mac.doFinal(data.toByteArray()),android.util.Base64.DEFAULT)
}

Bonus: HTML/JS for redirecting back to your app

As I mentioned earlier, Facebook and Twitter don’t accept a scheme as a Redirect Uri, so the workaround is to have them send the token back to a web page we host ourselves and from that web page trigger the a redirect to our app using the scheme.

Based on the config we used earlier, Facebook and Twitter will send the token back to https://mywebsite.com/oauth so this page needs to have something as follow:

<!doctype html>
<html lang="en">
<head>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("redirectButton").click();
setTimeout(function() {
window.location.href = "{{ path('homepage') }}";
}, 10000);
});
</script>
</head>
<body>
<p>Redirecting to May Day app, it will only take few seconds...</p>
<a href="{{ redirect_url }}" id="redirectButton">Click here to go back to May Day App</a>
</body>
</html>

Here I use twig templating and Symfony on the backend, my action looks as follow, but this is fairly easy to implement no matter what stack you use.

public function oAuthRedirectAction(Request $request)
{
$uri = $request->getRequestUri();
$uriParts = explode("?",$uri);
$params = "";
if(count($uriParts)>1){
$params = $uriParts[1];
}
return $this->render('@App/Home/oauth_redirect.twig',
[
"redirect_url"=>"google_client_id:/oauth2redirect?".$params
]
);
}

Conclusion

I hope you found this article interesting, it can be challenging at first but remember to check the Git Repo and explore it but also leave a comment if you think I can improve something.

Also in my case, this was just the first step to the authentication as I would use the Access Token to verify that the user is legit and then use the token to get the user’s info and register him on my own.

Cheers,

--

--

Hamza El Yousfi

Software engineer with focus on product value, fan of new technologies. Like to try out new tech and see how it can be integrated into on-going projects.