Skip to content

Instantly share code, notes, and snippets.

@kpmeen
Created March 13, 2016 16:30
Show Gist options
  • Save kpmeen/e34098276da841f41d8c to your computer and use it in GitHub Desktop.
Save kpmeen/e34098276da841f41d8c to your computer and use it in GitHub Desktop.
Getting emails for GitHub users after OAuth2 authentication.

What's this all about?

In some situations the GitHub oauth2 API does not return the user email in the access_token response. One such situation is if a user has chosen to set the email visibilty to Don't show my email address in the https://github.com/settings/profile page.

To ensure you get the primary email value populated in your internal User model. It is necessary to call a service in the GitHub API to fetch the value.

The URL is: https://api.github.com/user/emails?access_token=<the_access_token_from_authentication>

class LoginController @Inject() (
// ...
socialProviderRegistry: SocialProviderRegistry,
configuration: Configuration,
// ...
) extends Silhouette[User, JWTAuthenticator] {
private val log = Logger(this.getClass)
// Fetch the email from the GitHub REST API
private def fetchEmail(socialUid: String, provider: SocialProvider, a: AuthInfo): Future[Option[String]] = {
log.debug(s"Could not find any email for $socialUid in result. Going to looking up using the provider REST API")
val maybeUrl = configuration.getString(s"silhouette.${provider.id}.emailsURL")
maybeUrl.map(u =>
provider match {
case gh: GitHubProvider =>
log.debug(s"Trying to fetch a emails for $socialUid from GitHub.")
wsClient.url(u.format(a.asInstanceOf[OAuth2Info].accessToken)).get()
.map { response =>
val emails: Seq[GitHubEmail] = response.json.asOpt[Seq[GitHubEmail]].getOrElse(Seq.empty[GitHubEmail])
emails.find(_.primary).map(_.email)
}
.recover {
case err: Exception =>
log.warn(s"There was an error fetching emails for $socialUid from GitHub.")
None
}
case _ =>
Future.successful(None)
}).getOrElse(Future.successful(None))
}
// The authentication endpoint
def authenticate(provider: String) = Action.async { implicit request =>
(socialProviderRegistry.get[SocialProvider](provider) match {
case Some(p: SocialProvider with CommonSocialProfileBuilder) =>
p.authenticate().flatMap {
case Left(result) => Future.successful(result)
case Right(authInfo) =>
for {
profile <- p.retrieveProfile(authInfo)
maybeEmail <- if (profile.email.nonEmpty) Future.successful(profile.email) else fetchEmail(p.id, p, authInfo)
user <- fromSocialProfile(profile.copy(email = maybeEmail))
successOrFailure <- Future.successful(userService.save(user))
authInfo <- authInfoRepository.save(profile.loginInfo, authInfo)
authenticator <- env.authenticatorService.create(profile.loginInfo)
value <- env.authenticatorService.init(authenticator)
} yield {
env.eventBus.publish(LoginEvent(user, request, request2Messages))
Ok(Json.obj(
"token" -> value,
"expiresOn" -> Json.toJson[DateTime](authenticator.expirationDateTime)
))
}
}
case _ =>
Future.failed(new ProviderException(s"Social provider $provider is not supported"))
}).recover {
case e: ProviderException =>
log.error("Unexpected provider error", e)
Unauthorized(Json.obj("msg" -> e.getMessage))
}
}
private def fromSocialProfile(prof: CommonSocialProfile): Future[User] =
Future.successful {
val maybeUser = userService.findByUsername(Username(prof.loginInfo.providerKey))
User.updateFromCommonSocialProfile(prof, maybeUser)
}
}
case class GitHubEmail(email: String, primary: Boolean, verified: Boolean)
object GitHubEmail {
implicit val formats: Format[GitHubEmail] = Json.format[GitHubEmail]
}
silhouette {
# JWT authenticator settings
authenticator.headerName = "X-Auth-Token"
authenticator.issuerClaim = "<appname>"
authenticator.encryptSubject = true
authenticator.authenticatorExpiry = 12 hours
authenticator.sharedSecret= "changeme"
authenticator.rememberMe.authenticatorExpiry=30 days
authenticator.rememberMe.authenticatorIdleTimeout=5 days
# OAuth2 state provider settings
oauth2StateProvider.cookieName="OAuth2State"
oauth2StateProvider.cookiePath="/"
oauth2StateProvider.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set
oauth2StateProvider.httpOnlyCookie=true
oauth2StateProvider.expirationTime=5 minutes
# Github provider
github.authorizationURL="https://github.com/login/oauth/authorize"
github.accessTokenURL="https://github.com/login/oauth/access_token"
github.redirectURL="http://<your_apps_hostname>/authCallback/github"
github.clientID=""
github.clientID=${?GITHUB_CLIENT_ID}
github.clientSecret=""
github.clientSecret=${?GITHUB_CLIENT_SECRET}
github.scope="user:email"
github.emailsURL="https://api.github.com/user/emails?access_token=%s"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment