Flutter: Firebase GitHub Authentication

Flutter: Firebase GitHub Authentication

Firebase GitHub Authentication

Ever thought of how to implement GitHub Authentication with Firebase in your Flutter Project? In this article, I’ll show you how to implement it. This article is inspired by this article: Implementing Firebase GitHub Authentication in Flutter

Although the above article has a good explanation, I faced the issue with deep links and it does not have a source code. That is why I am writing this article.

Before starting, If you are new at Flutter Development, you can get started here: https://flutter.dev/docs/get-started/codelab

If you have successfully created the app then Add Firebase to your Flutter app.

I suppose you have already done the above step, so let’s begin. These are the steps that I will be covering up:

  1. Create an OAuth app on GitHub

  2. Enable GitHub Sign-in provider in Firebase console

  3. Deep links (Android) and Custom URL schemes (iOS)

  4. Coding ( Launch GitHub login on the browser and Handling the incoming data after successful login)

1. Create an OAuth app on GitHub:

For GitHub Authentication, you need to **register a new OAuth application. Fill up all the required fields and in the Authorization callback URL field, you need to enter the authorization callback URL (e.g. my-app-12345.firebaseapp.com/__/auth/handler) when you enable the GitHub Sign-in provider in the Firebase console in the next step. After successfully registering your app, you will get the Client ID and Client Secret.**

The Client ID and Client Secret will be used in the next step when we enable the GitHub Sign-in provider in Firebase Console. And the authorization callback URL will be used to set up deep links configuration later.

2. Enable GitHub Sign-in provider in Firebase console:

In the Firebase console, open the Authentication section. On the Sign-in method tab, enable the GitHub provider. Add the Client ID and Client Secret from the previous step:

Make sure your Firebase OAuth redirect URI (e.g. my-app-12345.firebaseapp.com/__/auth/handler) is set as your Authorization callback URL as mentioned in the previous setup.

Add the dependency for the Firebase Authentication Android library to your module (app-level) Gradle file (usually app/build.gradle):

implementationcom.google.firebase:firebase-auth:19.3.2

If you haven’t yet specified your app’s SHA-1 fingerprint, do so from the Settings page of the Firebase console.

These links are simply web-browser-like-links that activate your app and may contain information that you can use to load specific section of the app or continue certain user activity from a website (or another app).

In this step, I’ll consider my-app-12345.firebaseapp.com/__/auth/handler as Authorization callback URL

For Android: Add the following intent filter to the AndroidManifest.xml file located under /android/app/src/main directory:

*<!-- Deep Links -->*
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  *<!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->*
  <data
    android:scheme="[YOUR_SCHEME]"
    android:host="[YOUR_HOST]" />
</intent-filter>

The YOUR_SCHEME and YOUR_HOST in my case will be: https and my-app-12345.firebaseapp.com

So the intent filter will look like this:

<intent-filter>
   <action android:name="android.intent.action.VIEW" />
   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />
   <!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
   <data
     android:scheme="https"
     android:host="my-app-12345.firebaseapp.com" />
</intent-filter>

For iOS: Add the following attributes into Info.plist file located under /ios/Runner directory:

<?xml ...>
<!-- ... other tags -->
<plist>
<dict>
  <!-- ... other tags -->
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLName</key>
      <string>[ANY_URL_NAME]</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>[YOUR_SCHEME]</string>
      </array>
    </dict>
  </array>
  <!-- ... other tags -->
</dict>
</plist>

The YOUR_SCHEME and ANY_URL_NAME in my case will be: https and my-app-12345.firebaseapp.com

So the attributes will be this:

<array>
  <dict>
   <key>CFBundleTypeRole</key>
   <string>Editor</string>
   <key>CFBundleURLName</key>
   <string>my-app-12345.firebaseapp.com</string>
   <key>CFBundleURLSchemes</key>
   <array>
    <string>https</string>
   </array>
  </dict>
</array>

Note: I cannot test the Custom URL schemes for iOS as I am Windows user and cannot use Xcode, but let me know if you find any issue with it.

4. Coding:

Here comes the most exciting part.

Add the following dependencies in your app.

**firebase_core**: ^0.5.0
**firebase_auth**: ^0.18.0+1
**url_launcher**: ^5.5.2
**uni_links**: ^0.4.0

Note: A lot of changes has occurred since the latest firebase packages update, so make sure to look into the new changes, I’ll cover a few of them here.

First of all, add your Client ID and Client Secret in a file called secret_keys.dart. You will use these keys may times.

library secretkeys;

const GITHUB_CLIENT_ID = "client_id";
const GITHUB_CLIENT_SECRET = "client_secret";

Now, create a function onClickGitHubLoginButton() in your login screen

**import** 'secret_keys.dart' as **SecretKey**;
**import** 'package:url_launcher/url_launcher.dart';

//..

void onClickGitHubLoginButton() async {
  const String url = "https://github.com/login/oauth/authorize" +
        "?client_id=" + SecretKey.GITHUB_CLIENT_ID +
        "&scope=public_repo%20read:user%20user:email";

  if (await canLaunch(url)) {
      await launch(
        url,
        forceSafariVC: false,
        forceWebView: false,
      );
    } else {
      print("CANNOT LAUNCH THIS URL!");
    }
  }

This will open up the browser for GitHub sign-in, you have to log in with your credentials.

After successfully logging in, you will get redirected to an URL like *https://my-app-12345.firebaseapp.com/__/auth/handler?code=AUTHENTICATION_CODE , this URL will be caught by the app as you have already configured Deep Link in your app in previous step and you will be redirected back to the app (or prompted to be redirected). The AUTHENTICATION_CODE is required for successful log in. For handling the incoming data or parse the URL to get the AUTHENTICATION_CODE,* check the following code:

final **AuthService authService** = AuthService();

//..

StreamSubscription _subs;

@override
void initState() {
  _initDeepLinkListener();
  super.initState();
}

@override
void dispose() {
  _disposeDeepLinkListener();
  super.dispose();
}

void _initDeepLinkListener() async {
  _subs = getLinksStream().listen((String link) {
    _checkDeepLink(link);
  }, cancelOnError: true);
}

void _checkDeepLink(String link) {
  if (link != null) {
    String code = link.substring(link.indexOf(RegExp('code=')) + 5);
    authService.loginWithGitHub(code)
      .then((firebaseUser) {
        print(firebaseUser.email);
        print(firebaseUser.photoURL);
        print("LOGGED IN AS: " + firebaseUser.displayName);
      }).catchError((e) {
        print("LOGIN ERROR: " + e.toString());
      });
  }
}

void _disposeDeepLinkListener() {
  if (_subs != null) {
    _subs.cancel();
    _subs = null;
  }
}

//..

In the above code, you have _initDeepLinkListener() in initState() which is listening to the link with getLinksStream() and pass it to _checkDeepLink() to get the AUTHENTICATION_CODE.

The _checkDeepLink() later pass the “code” in the loginWithGitHub() which is a method from a different class named AuthService. Your login screen is implemented now, so you will create the class AuthService:

**import **'dart:convert';
**import** 'package:http/http.dart' as **http**;
**import** 'package:firebase_github_authentication/secret_keys.dart' as **SecretKey**;

import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:firebase_github_authentication/model/github_login_request.dart';
import 'package:firebase_github_authentication/model/github_login_response.dart';

class AuthService{

final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;

Future<auth.User> loginWithGitHub(String code) async {
    //ACCESS TOKEN REQUEST
    final response = await http.post(
      "https://github.com/login/oauth/access_token",
      headers: {
        "Content-Type": "application/json",
        "Accept": "application/json"
      },
      body: jsonEncode(GitHubLoginRequest(
        clientId: SecretKey.GITHUB_CLIENT_ID,
        clientSecret: SecretKey.GITHUB_CLIENT_SECRET,
        code: code,
      )),
    );

GitHubLoginResponse loginResponse = GitHubLoginResponse.fromJson(json.decode(response.body));

//FIREBASE SIGNIN
    final auth.AuthCredential credential = auth.GithubAuthProvider.credential(loginResponse.accessToken);

    final auth.User user = (await _firebaseAuth.signInWithCredential(credential)).user;
    return user;
  }
}

In the above code, you have defined the FirebaseAuth.instance as _firebaseAuth and you are requesting for an access token by sending an HTTP POST call to the specified URL, whose request body contains the “code” you have sent in from the Login screen, along with the client ID and client secret.

Then, you are decoding the response body and using the access token to get the credentials and passing it to _firebaseAuth.signInWithCredential() to sign in to Firebase

Here are the GitHubLoginResponse and GitHubLoginRequest models that you used:

class **GitHubLoginResponse** {
  String accessToken;
  String tokenType;
  String scope;

GitHubLoginResponse({this.accessToken, this.tokenType, this.scope});

factory GitHubLoginResponse.fromJson(Map<String, dynamic> json) =>
      GitHubLoginResponse(
        accessToken: json["access_token"],
        tokenType: json["token_type"],
        scope: json["scope"],
      );
}

class GitHubLoginRequest {
  String clientId;
  String clientSecret;
  String code;

GitHubLoginRequest({this.clientId, this.clientSecret, this.code});

dynamic toJson() => {
        "client_id": clientId,
        "client_secret": clientSecret,
        "code": code,
      };
}

If you are logged in successfully, you should have a User (previously FirebaseUser) object in your hands.

Conclusion

Now you are logged into Firebase with your GitHub account! I hope you learned something new from this article.

Here is the GitHub repository for this project: himanshusharma89/firebase-github-authentication

Thank you for reading, if you enjoyed the article make sure to give a clap (👏)! You can connect with me on Twitter, LinkedIn and find some of my work on GitHub and Codepen. And for more such articles you can support me by buying me a coffee:

References:

uni_links | Flutter Package

Authenticate Using GitHub on Android | Firebase