Home iOS & Swift Books Server-Side Swift with Vapor

23
GitHub Authentication Written by Tim Condon

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

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 and client secret that you need:

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. Open ImperialController.swift and add the following under processGoogleLogin(request:token:):

func processGitHubLogin(request: Request, token: String)
  throws -> Future<ResponseEncodable> {
    return request.future(request.redirect(to: "/"))
}
guard let githubCallbackURL =
  Environment.get("GITHUB_CALLBACK_URL") else {
    fatalError("GitHub callback URL not set")
}
try router.oAuth(
  from: GitHub.self,
  authenticate: "login-github",
  callback: githubCallbackURL,
  completion: processGitHubLogin)

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.

struct GitHubUserInfo: Content {
  let name: String
  let login: String
}
extension GitHub {
  // 1
  static func getUser(on request: Request)
  	throws -> Future<GitHubUserInfo> {
  	  // 2
      var headers = HTTPHeaders()
      headers.bearerAuthorization =
        try BearerAuthorization(token: request.accessToken())

      // 3
      let githubUserAPIURL = "https://api.github.com/user"
      // 4
      return try request
        .client()
        .get(githubUserAPIURL, headers: headers)
        .map(to: GitHubUserInfo.self) { response in
          // 5
          guard response.http.status == .ok else {
            // 6
            if response.http.status == .unauthorized {
              throw Abort.redirect(to: "/login-github")
            } else {
              throw Abort(.internalServerError)
            }
          }
          // 7
          return try response.content
            .syncDecode(GitHubUserInfo.self)
      }
  }
}
// 1
return try GitHub
  .getUser(on: request)
  .flatMap(to: ResponseEncodable.self) { userInfo in
    // 2
    return User
      .query(on: request)
      .filter(\.username == userInfo.login)
      .first()
      .flatMap(to: ResponseEncodable.self) { 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)
            .map(to: ResponseEncodable.self) { user in
              // 5
              try request.authenticateSession(user)
              return request.redirect(to: "/")
          }
        }
        // 6
        try request.authenticateSession(existingUser)
        return request.future(request.redirect(to: "/"))
    }
}
<a href="/login-github">
  <img class="mt-3" src="/images/sign-in-with-github.png"
   alt="Sign In With GitHub">
</a>

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. This allows your users to choose a range of options for authentication.

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.