CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-typesafe-play--play-ws-2-10

Asynchronous HTTP client for Play Framework with OAuth, OpenID, and SSL/TLS support

Pending
Overview
Eval results
Files

openid.mddocs/

OpenID Authentication

OpenID authentication support for identity verification and user attribute exchange. Play WS provides comprehensive OpenID support for authenticating users with OpenID providers like Google, Yahoo, and others, with both Scala and Java APIs.

Capabilities

OpenID Client - Scala API

Main entry point for OpenID authentication operations.

/**
 * OpenID authentication client
 */
object OpenID {
  /**
   * Generate OpenID authentication redirect URL
   * @param openID The OpenID identifier (user's OpenID URL or provider URL)
   * @param callbackURL URL where the OpenID provider should redirect after authentication
   * @param axRequired Required attribute exchange parameters
   * @param axOptional Optional attribute exchange parameters  
   * @param realm Optional realm for the authentication request
   * @return Future containing the redirect URL
   */
  def redirectURL(
    openID: String,
    callbackURL: String,
    axRequired: Seq[(String, String)] = Seq.empty,
    axOptional: Seq[(String, String)] = Seq.empty,
    realm: Option[String] = None
  )(implicit app: Application): Future[String]
  
  /**
   * Verify OpenID authentication response and extract user information
   * @param request HTTP request containing OpenID response parameters
   * @return Future containing verified user information
   */
  def verifiedId(implicit request: Request[_], app: Application): Future[UserInfo]
}

Basic OpenID Flow Example:

import play.api.libs.openid._
import play.api.mvc._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class OpenIDController @Inject()(cc: ControllerComponents) 
  extends AbstractController(cc) {
  
  def login = Action.async { implicit request =>
    val openIdUrl = "https://www.google.com/accounts/o8/id"
    val callbackURL = routes.OpenIDController.callback().absoluteURL()
    
    // Request email and name attributes
    val axRequired = Seq(
      "email" -> "http://axschema.org/contact/email",
      "firstname" -> "http://axschema.org/namePerson/first",
      "lastname" -> "http://axschema.org/namePerson/last"
    )
    
    OpenID.redirectURL(
      openID = openIdUrl,
      callbackURL = callbackURL,
      axRequired = axRequired
    ).map { redirectUrl =>
      Redirect(redirectUrl)
    }.recover {
      case ex =>
        BadRequest(s"OpenID error: ${ex.getMessage}")
    }
  }
  
  def callback = Action.async { implicit request =>
    OpenID.verifiedId.map { userInfo =>
      // User successfully authenticated
      Ok(s"Welcome ${userInfo.id}!")
        .withSession("openid" -> userInfo.id)
    }.recover {
      case ex =>
        BadRequest(s"Authentication failed: ${ex.getMessage}")
    }
  }
}

OpenID Client - Java API

Main entry point for OpenID authentication operations in Java.

/**
 * OpenID authentication client for Java
 */
public class OpenID {
  /**
   * Generate OpenID authentication redirect URL
   * @param openID The OpenID identifier (user's OpenID URL or provider URL)
   * @param callbackURL URL where the OpenID provider should redirect after authentication
   * @param axRequired Required attribute exchange parameters
   * @param axOptional Optional attribute exchange parameters  
   * @param realm Optional realm for the authentication request
   * @return Promise containing the redirect URL
   */
  public static Promise<String> redirectURL(
    String openID,
    String callbackURL,
    Map<String, String> axRequired,
    Map<String, String> axOptional,
    String realm
  )
  
  public static Promise<String> redirectURL(String openID, String callbackURL)
  
  /**
   * Verify OpenID authentication response and extract user information
   * @param request HTTP request containing OpenID response parameters
   * @return Promise containing verified user information
   */
  public static Promise<UserInfo> verifiedId(Http.Request request)
}

Java OpenID Flow Example:

import play.libs.openid.*;
import play.libs.F.Promise;
import play.mvc.*;

public class AuthController extends Controller {
    
    public Result login() {
        return ok(views.html.login.render());
    }
    
    public Promise<Result> authenticate() {
        // Step 1: Generate redirect URL for OpenID provider
        String openidURL = "https://www.google.com/accounts/o8/id";
        String callbackURL = routes.AuthController.openidCallback().absoluteURL(request());
        
        // Set up attribute exchange
        Map<String, String> axRequired = new HashMap<>();
        axRequired.put("email", "http://axschema.org/contact/email");
        axRequired.put("firstname", "http://axschema.org/namePerson/first");
        axRequired.put("lastname", "http://axschema.org/namePerson/last");
        
        return OpenID.redirectURL(openidURL, callbackURL, axRequired, new HashMap<>(), null)
            .map(url -> redirect(url));
    }
    
    public Promise<Result> openidCallback() {
        // Step 2: Verify the OpenID response
        return OpenID.verifiedId(request())
            .map(userInfo -> {
                // Authentication successful - create session
                return redirect(routes.Application.index())
                    .withSession("openid", userInfo.getId())
                    .withSession("email", userInfo.getAttributes().get("email"));
            })
            .recover(throwable -> {
                return badRequest("Authentication failed: " + throwable.getMessage());
            });
    }
}

OpenID Client Interface - Scala API

Injectable OpenID client interface for dependency injection.

/**
 * OpenID client interface for dependency injection
 */
trait OpenIdClient {
  /**
   * Generate authentication redirect URL
   */
  def redirectURL(
    openID: String,
    callbackURL: String,
    axRequired: Seq[(String, String)] = Seq.empty,
    axOptional: Seq[(String, String)] = Seq.empty,
    realm: Option[String] = None
  ): Future[String]
  
  /**
   * Verify authentication response
   */
  def verifiedId(request: RequestHeader): Future[UserInfo]
}

User Information

User information returned after successful OpenID authentication.

/**
 * OpenID user information
 * @param id The verified OpenID identifier
 * @param attributes Additional user attributes from attribute exchange
 */
case class UserInfo(id: String, attributes: Map[String, String] = Map.empty)

User Information Usage:

OpenID.verifiedId.map { userInfo =>
  val openIdIdentifier = userInfo.id
  val email = userInfo.attributes.get("email")
  val firstName = userInfo.attributes.get("firstname")
  val lastName = userInfo.attributes.get("lastname")
  
  // Create or update user profile
  val user = User(
    openId = openIdIdentifier,
    email = email.getOrElse(""),
    name = s"${firstName.getOrElse("")} ${lastName.getOrElse("")}".trim
  )
  
  // Store user session
  Ok("Login successful").withSession("user_id" -> user.id.toString)
}

OpenID Server Discovery

OpenID server information discovered from provider endpoints.

/**
 * OpenID server information
 * @param protocolVersion OpenID protocol version
 * @param url Server endpoint URL
 * @param delegate Optional delegate identifier
 */
case class OpenIDServer(
  protocolVersion: String,
  url: String,
  delegate: Option[String]
)

Discovery Interface

Server discovery interface for OpenID provider endpoints.

/**
 * OpenID server discovery interface
 */
trait Discovery {
  /**
   * Discover OpenID server information from provider
   * @param openID OpenID identifier or provider URL
   * @return Future containing server information
   */
  def discoverServer(openID: String): Future[OpenIDServer]
  
  /**
   * Normalize OpenID identifier according to specification
   * @param openID Raw OpenID identifier
   * @return Normalized identifier
   */
  def normalizeIdentifier(openID: String): String
}

Error Handling

OpenID-specific error types and handling.

/**
 * OpenID error base class
 */
sealed abstract class OpenIDError extends Throwable

/**
 * OpenID error types
 */
object Errors {
  case object MISSING_PARAMETERS extends OpenIDError
  case object AUTH_ERROR extends OpenIDError  
  case object AUTH_CANCEL extends OpenIDError
  case object BAD_RESPONSE extends OpenIDError
  case object NO_SERVER extends OpenIDError
  case object NETWORK_ERROR extends OpenIDError
}

Error Handling Example:

import play.api.libs.openid.Errors._

OpenID.verifiedId.map { userInfo =>
  // Success case
  Ok(s"Welcome ${userInfo.id}")
}.recover {
  case AUTH_CANCEL =>
    Redirect(routes.Application.index())
      .flashing("warning" -> "Authentication was cancelled")
      
  case AUTH_ERROR =>
    BadRequest("Authentication failed")
    
  case NO_SERVER =>
    BadRequest("Invalid OpenID provider")
    
  case NETWORK_ERROR =>
    InternalServerError("Network error during authentication")
    
  case ex: OpenIDError =>
    BadRequest(s"OpenID error: ${ex.getClass.getSimpleName}")
    
  case ex =>
    InternalServerError(s"Unexpected error: ${ex.getMessage}")
}

Attribute Exchange

Request user attributes from OpenID providers supporting attribute exchange.

Common Attribute Exchange Parameters:

// Standard AX schema URLs
val axAttributes = Map(
  "email" -> "http://axschema.org/contact/email",
  "firstname" -> "http://axschema.org/namePerson/first",
  "lastname" -> "http://axschema.org/namePerson/last",
  "fullname" -> "http://axschema.org/namePerson",
  "nickname" -> "http://axschema.org/namePerson/friendly",
  "country" -> "http://axschema.org/contact/country/home",
  "language" -> "http://axschema.org/pref/language",
  "timezone" -> "http://axschema.org/pref/timezone"
)

// Request required attributes
val axRequired = Seq(
  "email" -> axAttributes("email"),
  "name" -> axAttributes("fullname")
)

// Request optional attributes  
val axOptional = Seq(
  "nickname" -> axAttributes("nickname"),
  "country" -> axAttributes("country")
)

OpenID.redirectURL(
  openID = "https://www.google.com/accounts/o8/id",
  callbackURL = callbackURL,
  axRequired = axRequired,
  axOptional = axOptional
)

Provider-Specific Examples

Google OpenID:

def loginWithGoogle = Action.async { implicit request =>
  val googleOpenId = "https://www.google.com/accounts/o8/id"
  val callbackURL = routes.OpenIDController.googleCallback().absoluteURL()
  
  val axRequired = Seq(
    "email" -> "http://axschema.org/contact/email",
    "firstname" -> "http://axschema.org/namePerson/first", 
    "lastname" -> "http://axschema.org/namePerson/last"
  )
  
  OpenID.redirectURL(googleOpenId, callbackURL, axRequired).map { url =>
    Redirect(url)
  }
}

Yahoo OpenID:

def loginWithYahoo = Action.async { implicit request =>
  val yahooOpenId = "https://me.yahoo.com"
  val callbackURL = routes.OpenIDController.yahooCallback().absoluteURL()
  
  val axRequired = Seq(
    "email" -> "http://axschema.org/contact/email",
    "fullname" -> "http://axschema.org/namePerson"
  )
  
  OpenID.redirectURL(yahooOpenId, callbackURL, axRequired).map { url =>
    Redirect(url)
  }
}

Custom OpenID Provider:

def loginWithCustom(openIdUrl: String) = Action.async { implicit request =>
  val callbackURL = routes.OpenIDController.customCallback().absoluteURL()
  
  // Validate OpenID URL format
  if (!openIdUrl.startsWith("http://") && !openIdUrl.startsWith("https://")) {
    Future.successful(BadRequest("Invalid OpenID URL"))
  } else {
    OpenID.redirectURL(openIdUrl, callbackURL).map { url =>
      Redirect(url)
    }.recover {
      case ex =>
        BadRequest(s"Invalid OpenID provider: ${ex.getMessage}")
    }
  }
}

Complete OpenID Integration

import play.api.libs.openid._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class OpenIDAuthController @Inject()(cc: ControllerComponents) 
  extends AbstractController(cc) {
  
  val openIdForm = Form(
    "openid_identifier" -> nonEmptyText
  )
  
  def loginForm = Action { implicit request =>
    Ok(views.html.openid.login(openIdForm))
  }
  
  def authenticate = Action.async { implicit request =>
    openIdForm.bindFromRequest.fold(
      formWithErrors => {
        Future.successful(BadRequest(views.html.openid.login(formWithErrors)))
      },
      openIdIdentifier => {
        val callbackURL = routes.OpenIDAuthController.callback().absoluteURL()
        
        // Standard attributes to request
        val axRequired = Seq(
          "email" -> "http://axschema.org/contact/email"
        )
        
        val axOptional = Seq(
          "firstname" -> "http://axschema.org/namePerson/first",
          "lastname" -> "http://axschema.org/namePerson/last",
          "fullname" -> "http://axschema.org/namePerson"
        )
        
        OpenID.redirectURL(
          openID = openIdIdentifier,
          callbackURL = callbackURL,
          axRequired = axRequired,
          axOptional = axOptional,
          realm = Some(request.host)
        ).map { redirectUrl =>
          Redirect(redirectUrl)
        }.recover {
          case ex =>
            BadRequest(views.html.openid.login(
              openIdForm.withGlobalError(s"OpenID error: ${ex.getMessage}")
            ))
        }
      }
    )
  }
  
  def callback = Action.async { implicit request =>
    OpenID.verifiedId.map { userInfo =>
      // Extract user information
      val openId = userInfo.id
      val email = userInfo.attributes.get("email").getOrElse("")
      val name = userInfo.attributes.get("fullname")
        .orElse(userInfo.attributes.get("firstname").map(_ + " " + userInfo.attributes.get("lastname").getOrElse("")))
        .getOrElse("Unknown")
      
      // In a real app, you would save this to your user database
      // For this example, we'll just store in session
      Redirect(routes.Application.profile())
        .withSession(
          "openid" -> openId,
          "email" -> email,
          "name" -> name
        )
        .flashing("success" -> "Successfully logged in with OpenID")
        
    }.recover {
      case Errors.AUTH_CANCEL =>
        Redirect(routes.OpenIDAuthController.loginForm())
          .flashing("warning" -> "Authentication was cancelled")
          
      case Errors.AUTH_ERROR =>
        Redirect(routes.OpenIDAuthController.loginForm())
          .flashing("error" -> "Authentication failed")
          
      case ex =>
        Redirect(routes.OpenIDAuthController.loginForm())
          .flashing("error" -> s"Authentication error: ${ex.getMessage}")
    }
  }
  
  def logout = Action { implicit request =>
    Redirect(routes.Application.index())
      .withNewSession
      .flashing("success" -> "You have been logged out")
  }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-typesafe-play--play-ws-2-10

docs

http-client.md

index.md

ning.md

oauth.md

openid.md

ssl.md

testing.md

tile.json