Chapters

Hide chapters

Server-Side Swift with Vapor

Third Edition · iOS 13 · Swift 5.2 - Vapor 4 Framework · Xcode 11.4

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: Creating a Simple Web API

Section 1: 13 chapters
Show chapters Hide chapters

23. GitHub Authentication
Written by Tim Condon

In the previous chapter, you learned how to authenticate users using Google. In this chapter, you’ll see how to build upon this and allow users to log in with their GitHub accounts.

Setting up your application with GitHub

To be able to use GitHub OAuth in your application, you must first register the application with GitHub. In your browser, go to https://github.com/settings/developers. Click Register a new application:

Note: You must have a GitHub account to complete this chapter. If you don’t have one, visit https://github.com/join to create one. This chapter also assumes you added Imperial as a dependency to your project in the previous chapter.

Fill in the form with an appropriate name, e.g. Vapor TIL. Set the Homepage URL to http://localhost:8080 for this application and provide a sensible description. Set the Authorization callback URL to http://localhost:8080/oauth/github. This is the URL that GitHub redirects back to once users have allowed your application access to their data:

Click Register application. After it creates the application, the site takes you back to the application’s information page. That page provides the client ID. Click Generate a new client secret to get a client secret:

Note: You must keep these safe and secure. Your secret allows you access to GitHub’s APIs and you should not share or check the secret into source control. You should treat it like a password.

Integrating with Imperial

Now that you’ve registered your application with GitHub, you can start integrating Imperial. First, open Package.swift in Xcode and replace:

.product(name: "ImperialGoogle", package: "Imperial")

with the following:

.product(name: "ImperialGoogle", package: "Imperial"),
.product(name: "ImperialGitHub", package: "Imperial")

This adds Imperial’s GitHub library as a dependency. Next, open ImperialController.swift and add the following below import Fluent:

import ImperialGitHub

This allows your code to see Imperial’s GitHub functions. Next, add the following under processGoogleLogin(request:token:):

func processGitHubLogin(request: Request, token: String)
  throws -> EventLoopFuture<ResponseEncodable> {
    return request.eventLoop.future(request.redirect(to: "/"))
  }

This defines a method to handle the GitHub login, similar to the initial handler for Google logins. The handler simply redirects the user to the home page. Imperial uses this method as the final callback once it has handled the GitHub redirect.

Next, set up the Imperial routes by adding the following at the bottom of boot(routes:):

guard let githubCallbackURL =
  Environment.get("GITHUB_CALLBACK_URL") else {
    fatalError("GitHub callback URL not set")
}
try routes.oAuth(
  from: GitHub.self,
  authenticate: "login-github",
  callback: githubCallbackURL,
  completion: processGitHubLogin)

Here’s what this does:

  • Get the callback URL from an environment variable — this is the URL you set up when registering the application with GitHub.
  • Register Imperial’s GitHub OAuth router with your app’s routes.
  • Tell Imperial to use the GitHub handler.
  • Set up the /login-github request as the route that triggers the OAuth flow. This is the route the application uses to allow users to log in via GitHub.
  • Provide the callback URL to Imperial.
  • Set the completion handler to processGitHubLogin(request:token:) — the method you created above.

As before, you need to provide Imperial the client ID and client secret that GitHub gave you using environment variables. You must also provide the redirect URL. Open .env in a text editor and add the following at the bottom of the file:

GITHUB_CALLBACK_URL=http://localhost:8080/oauth/github
GITHUB_CLIENT_ID=<YOUR_GITHUB_CLIENT_ID>
GITHUB_CLIENT_SECRET=<YOUR_GITHUB_CLIENT_SECRET>

Add the client ID and secret generate by GitHub earlier.

Note: Be sure you still have environment variables set for GOOGLE_CALLBACK_URL, GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET or your app won’t start.

Integrating with web authentication

As in the previous chapter, it’s important to match the experience for a regular login. Again, you’ll create a new user when a user logs in with GitHub for the first time. You can use GitHub’s API with the user’s OAuth token.

At the bottom of ImperialController.swift, add a new type to decode the data from GitHub’s API:

struct GitHubUserInfo: Content {
  let name: String
  let login: String
}

The request to GitHub’s API returns many fields. However, you only care about the login, which becomes the username, and the name.

Next, under GitHubUserInfo, add the following:

extension GitHub {
  // 1
  static func getUser(on request: Request)
    throws -> EventLoopFuture<GitHubUserInfo> {
      // 2
      var headers = HTTPHeaders()
      try headers.add(
        name: .authorization, 
        value: "token \(request.accessToken())")
      headers.add(name: .userAgent, value: "vapor")

      // 3
      let githubUserAPIURL: URI = "https://api.github.com/user"
      // 4
      return request
        .client
        .get(githubUserAPIURL, headers: headers)
        .flatMapThrowing { response in
          // 5
          guard response.status == .ok else {
            // 6
            if response.status == .unauthorized {
              throw Abort.redirect(to: "/login-github")
            } else {
              throw Abort(.internalServerError)
            }
          }
          // 7
          return try response.content
            .decode(GitHubUserInfo.self)
      }
  }
}

Here’s what this does:

  1. Add a new method to Imperial’s GitHub service which gets a user’s details from the GitHub API.
  2. Set the headers for the request by adding the OAuth token to the authorization header. Note that GitHub doesn’t use a standard bearer authorization header, so you must define the header manually. Also set the user-agent header as GitHub’s API requires this.
  3. Set the URL for the request — this is GitHub’s API to get the user’s information. This uses Vapor’s URI which the Client requires.
  4. Use request.client to send an HTTP request. get() sends an HTTP GET request to the URL provided. Unwrap the returned future response.
  5. Ensure the response status is 200 OK.
  6. Otherwise, return to the login page if the response was 401 Unauthorized or return an error.
  7. Decode the data from the response to GitHub and return the result.

Next, replace the body of processGitHubLogin(request:token:) with the following:

// 1
return try GitHub
  .getUser(on: request)
  .flatMap { userInfo in
    // 2
    return User
      .query(on: request.db)
      .filter(\.$username == userInfo.login)
      .first()
      .flatMap { foundUser in
        guard let existingUser = foundUser else {
          // 3
          let user = User(
            name: userInfo.name,
            username: userInfo.login,
            password: UUID().uuidString)
          // 4
          return user
            .save(on: request.db)
            .flatMap {
              // 5
              request.session.authenticate(user)
              return generateRedirect(on: request, for: user)
          }
        }
        // 6
        request.session.authenticate(existingUser)
        return generateRedirect(on: request, for: existingUser)
    }
}

Here’s what the new code does:

  1. Get the user information from GitHub.

  2. See if the user exists in the database by looking up the login property as the username.

  3. If the user doesn’t exist, create a new User using the name and username from the user information from GitHub. Set the password to a UUID, since you don’t need it.

  4. Save the user and unwrap the returned future.

  5. Call session.authenticate(_:) on Request to save the created user in the session so the website allows access. Use generateRedirect(on:for:) from the previous chapter to redirect back to the home page.

  6. If the user already exists, authenticate the user in the session and redirect to the home page. Again, use generateRedirect(on:for:) to create the redirect.

The final thing to do is to add a button on the website to allow users to make use of the new functionality! Open login.leaf and, under </form>, add the following:

<a href="/login-github">
  <img class="mt-3" src="/images/sign-in-with-github.png"
   alt="Sign In With GitHub">
</a>

The sample project for this chapter contains a new image, sign-in-with-github.png, to display a Sign in with GitHub button. This adds the image as a link to /login-github — the route provided to Imperial to start the login. Build and run the application and then visit http://localhost:8080 in your browser. Click Create An Acronym and the application takes you to the login page. You’ll see the new Sign in with GitHub button next to the Sign in with Google button:

Click the new button and the application takes you to a GitHub page to allow the TIL application access to your information:

Click the Authorize button you see there and the application redirects you back to the home page. Go to the All Users screen and you’ll see your new user account. If you create an acronym, the application also uses that new user.

Integrating with iOS

Just like signing in with Google, you should offer the ability to sign in with GitHub on iOS as well. You’ve already done most of this work in the previous chapter :]

Below iOSGoogleLogin(_:), create a new route handler for logging in on iOS with GitHub:

func iOSGitHubLogin(_ req: Request) -> Response {
  // 1
  req.session.data["oauth_login"] = "iOS"
  // 2
  return req.redirect(to: "/login-github")
}

This new route handler does two things:

  1. Sets a flag in the request’s session to mark this as an iOS log in attempt.
  2. Redirect to /login-github to trigger the OAuth flow with GitHub.

Register the new route at the bottom of boot(routes:):

routes.get("iOS", "login-github", use: iOSGitHubLogin)

This routes a GET request to /iOS/login-github to iosGitHubLogin(_:). That’s all you need to do in TILApp to support iOS log in with GitHub! Build and run the project and open the iOS starter project for this chapter.

The iOS starter project for the chapter contains a new button on the log in page for GitHub. There’s a corresponding method in LoginTableViewController.swift to invoke when a user taps the new button. Add the following to signInWithGithubButtonTapped(_:):

// 1
guard let githubAuthURL =
  URL(string: "http://localhost:8080/iOS/login-github") 
else {
  return
}
// 2
let scheme = "tilapp"
// 3
let session = ASWebAuthenticationSession(
  url: githubAuthURL, 
  callbackURLScheme: scheme) { callbackURL, error in
  // 4
  guard 
    error == nil, 
    let callbackURL = callbackURL 
  else { 
    return 
  }

  let queryItems = URLComponents(
    string: callbackURL.absoluteString
  )?.queryItems
  let token = queryItems?.first { $0.name == "token" }?.value
  // 5
  Auth().token = token
  // 6
  DispatchQueue.main.async {
    let appDelegate = 
      UIApplication.shared.delegate as? AppDelegate
    appDelegate?.window?.rootViewController =
      UIStoryboard(
        name: "Main", 
        bundle: Bundle.main).instantiateInitialViewController()
  }
}
// 7
session.presentationContextProvider = self
session.start()

Here’s what the new code does:

  1. Create a URL for logging in with GitHub. This is the URL you created in TILApp earlier.
  2. Set the scheme to tilapp — this is the scheme you redirect to. For more information see Chapter 22, “Google Authentication”.
  3. Create an instance of ASWebAuthenticationSession using the scheme and URL.
  4. Ensure there’s a callback URL and no error. Extract the token from the callback URL.
  5. Set the token in the keychain using Auth.
  6. Finish logging in the user by changing the root view controller to the main navigation view controller.
  7. Set presentationContextProvider to the LoginViewController and start the session.

Build and run the app and log out in the Users tab if necessary. On the log in page, you’ll see the new Sign in with GitHub button:

Tap the new button and the app asks you to confirm that you want to use TILApp to log in:

Tap Continue. If you’re already logged into GitHub on the simulator, the app logs you straight in. Otherwise you’ll see the OAuth screen for GitHub to allow access:

Log in to GitHub and if you’ve approved TILApp the app logs you in. Otherwise GitHub asks you to confirm access for the TIL app, just like the website. Once confirmed, the app logs you in.

Where to go from here?

In this chapter, you learned how to integrate GitHub login into your website using Imperial and OAuth. This complements the Google and first-party sign in experiences and allows your users to choose a range of options for authentication.

In the next chapter, you’ll learn how to implement Sign in with Apple, giving your users a third option for using an external authentication service to register with your app.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.