or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

http-client.mdindex.mdning.mdoauth.mdopenid.mdssl.mdtesting.md

openid.mddocs/

0

# OpenID Authentication

1

2

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.

3

4

## Capabilities

5

6

### OpenID Client - Scala API

7

8

Main entry point for OpenID authentication operations.

9

10

```scala { .api }

11

/**

12

* OpenID authentication client

13

*/

14

object OpenID {

15

/**

16

* Generate OpenID authentication redirect URL

17

* @param openID The OpenID identifier (user's OpenID URL or provider URL)

18

* @param callbackURL URL where the OpenID provider should redirect after authentication

19

* @param axRequired Required attribute exchange parameters

20

* @param axOptional Optional attribute exchange parameters

21

* @param realm Optional realm for the authentication request

22

* @return Future containing the redirect URL

23

*/

24

def redirectURL(

25

openID: String,

26

callbackURL: String,

27

axRequired: Seq[(String, String)] = Seq.empty,

28

axOptional: Seq[(String, String)] = Seq.empty,

29

realm: Option[String] = None

30

)(implicit app: Application): Future[String]

31

32

/**

33

* Verify OpenID authentication response and extract user information

34

* @param request HTTP request containing OpenID response parameters

35

* @return Future containing verified user information

36

*/

37

def verifiedId(implicit request: Request[_], app: Application): Future[UserInfo]

38

}

39

```

40

41

**Basic OpenID Flow Example:**

42

43

```scala

44

import play.api.libs.openid._

45

import play.api.mvc._

46

import scala.concurrent.Future

47

import scala.concurrent.ExecutionContext.Implicits.global

48

49

class OpenIDController @Inject()(cc: ControllerComponents)

50

extends AbstractController(cc) {

51

52

def login = Action.async { implicit request =>

53

val openIdUrl = "https://www.google.com/accounts/o8/id"

54

val callbackURL = routes.OpenIDController.callback().absoluteURL()

55

56

// Request email and name attributes

57

val axRequired = Seq(

58

"email" -> "http://axschema.org/contact/email",

59

"firstname" -> "http://axschema.org/namePerson/first",

60

"lastname" -> "http://axschema.org/namePerson/last"

61

)

62

63

OpenID.redirectURL(

64

openID = openIdUrl,

65

callbackURL = callbackURL,

66

axRequired = axRequired

67

).map { redirectUrl =>

68

Redirect(redirectUrl)

69

}.recover {

70

case ex =>

71

BadRequest(s"OpenID error: ${ex.getMessage}")

72

}

73

}

74

75

def callback = Action.async { implicit request =>

76

OpenID.verifiedId.map { userInfo =>

77

// User successfully authenticated

78

Ok(s"Welcome ${userInfo.id}!")

79

.withSession("openid" -> userInfo.id)

80

}.recover {

81

case ex =>

82

BadRequest(s"Authentication failed: ${ex.getMessage}")

83

}

84

}

85

}

86

```

87

88

### OpenID Client - Java API

89

90

Main entry point for OpenID authentication operations in Java.

91

92

```java { .api }

93

/**

94

* OpenID authentication client for Java

95

*/

96

public class OpenID {

97

/**

98

* Generate OpenID authentication redirect URL

99

* @param openID The OpenID identifier (user's OpenID URL or provider URL)

100

* @param callbackURL URL where the OpenID provider should redirect after authentication

101

* @param axRequired Required attribute exchange parameters

102

* @param axOptional Optional attribute exchange parameters

103

* @param realm Optional realm for the authentication request

104

* @return Promise containing the redirect URL

105

*/

106

public static Promise<String> redirectURL(

107

String openID,

108

String callbackURL,

109

Map<String, String> axRequired,

110

Map<String, String> axOptional,

111

String realm

112

)

113

114

public static Promise<String> redirectURL(String openID, String callbackURL)

115

116

/**

117

* Verify OpenID authentication response and extract user information

118

* @param request HTTP request containing OpenID response parameters

119

* @return Promise containing verified user information

120

*/

121

public static Promise<UserInfo> verifiedId(Http.Request request)

122

}

123

```

124

125

**Java OpenID Flow Example:**

126

127

```java

128

import play.libs.openid.*;

129

import play.libs.F.Promise;

130

import play.mvc.*;

131

132

public class AuthController extends Controller {

133

134

public Result login() {

135

return ok(views.html.login.render());

136

}

137

138

public Promise<Result> authenticate() {

139

// Step 1: Generate redirect URL for OpenID provider

140

String openidURL = "https://www.google.com/accounts/o8/id";

141

String callbackURL = routes.AuthController.openidCallback().absoluteURL(request());

142

143

// Set up attribute exchange

144

Map<String, String> axRequired = new HashMap<>();

145

axRequired.put("email", "http://axschema.org/contact/email");

146

axRequired.put("firstname", "http://axschema.org/namePerson/first");

147

axRequired.put("lastname", "http://axschema.org/namePerson/last");

148

149

return OpenID.redirectURL(openidURL, callbackURL, axRequired, new HashMap<>(), null)

150

.map(url -> redirect(url));

151

}

152

153

public Promise<Result> openidCallback() {

154

// Step 2: Verify the OpenID response

155

return OpenID.verifiedId(request())

156

.map(userInfo -> {

157

// Authentication successful - create session

158

return redirect(routes.Application.index())

159

.withSession("openid", userInfo.getId())

160

.withSession("email", userInfo.getAttributes().get("email"));

161

})

162

.recover(throwable -> {

163

return badRequest("Authentication failed: " + throwable.getMessage());

164

});

165

}

166

}

167

```

168

169

### OpenID Client Interface - Scala API

170

171

Injectable OpenID client interface for dependency injection.

172

173

```scala { .api }

174

/**

175

* OpenID client interface for dependency injection

176

*/

177

trait OpenIdClient {

178

/**

179

* Generate authentication redirect URL

180

*/

181

def redirectURL(

182

openID: String,

183

callbackURL: String,

184

axRequired: Seq[(String, String)] = Seq.empty,

185

axOptional: Seq[(String, String)] = Seq.empty,

186

realm: Option[String] = None

187

): Future[String]

188

189

/**

190

* Verify authentication response

191

*/

192

def verifiedId(request: RequestHeader): Future[UserInfo]

193

}

194

```

195

196

### User Information

197

198

User information returned after successful OpenID authentication.

199

200

```scala { .api }

201

/**

202

* OpenID user information

203

* @param id The verified OpenID identifier

204

* @param attributes Additional user attributes from attribute exchange

205

*/

206

case class UserInfo(id: String, attributes: Map[String, String] = Map.empty)

207

```

208

209

**User Information Usage:**

210

211

```scala

212

OpenID.verifiedId.map { userInfo =>

213

val openIdIdentifier = userInfo.id

214

val email = userInfo.attributes.get("email")

215

val firstName = userInfo.attributes.get("firstname")

216

val lastName = userInfo.attributes.get("lastname")

217

218

// Create or update user profile

219

val user = User(

220

openId = openIdIdentifier,

221

email = email.getOrElse(""),

222

name = s"${firstName.getOrElse("")} ${lastName.getOrElse("")}".trim

223

)

224

225

// Store user session

226

Ok("Login successful").withSession("user_id" -> user.id.toString)

227

}

228

```

229

230

### OpenID Server Discovery

231

232

OpenID server information discovered from provider endpoints.

233

234

```scala { .api }

235

/**

236

* OpenID server information

237

* @param protocolVersion OpenID protocol version

238

* @param url Server endpoint URL

239

* @param delegate Optional delegate identifier

240

*/

241

case class OpenIDServer(

242

protocolVersion: String,

243

url: String,

244

delegate: Option[String]

245

)

246

```

247

248

### Discovery Interface

249

250

Server discovery interface for OpenID provider endpoints.

251

252

```scala { .api }

253

/**

254

* OpenID server discovery interface

255

*/

256

trait Discovery {

257

/**

258

* Discover OpenID server information from provider

259

* @param openID OpenID identifier or provider URL

260

* @return Future containing server information

261

*/

262

def discoverServer(openID: String): Future[OpenIDServer]

263

264

/**

265

* Normalize OpenID identifier according to specification

266

* @param openID Raw OpenID identifier

267

* @return Normalized identifier

268

*/

269

def normalizeIdentifier(openID: String): String

270

}

271

```

272

273

### Error Handling

274

275

OpenID-specific error types and handling.

276

277

```scala { .api }

278

/**

279

* OpenID error base class

280

*/

281

sealed abstract class OpenIDError extends Throwable

282

283

/**

284

* OpenID error types

285

*/

286

object Errors {

287

case object MISSING_PARAMETERS extends OpenIDError

288

case object AUTH_ERROR extends OpenIDError

289

case object AUTH_CANCEL extends OpenIDError

290

case object BAD_RESPONSE extends OpenIDError

291

case object NO_SERVER extends OpenIDError

292

case object NETWORK_ERROR extends OpenIDError

293

}

294

```

295

296

**Error Handling Example:**

297

298

```scala

299

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

300

301

OpenID.verifiedId.map { userInfo =>

302

// Success case

303

Ok(s"Welcome ${userInfo.id}")

304

}.recover {

305

case AUTH_CANCEL =>

306

Redirect(routes.Application.index())

307

.flashing("warning" -> "Authentication was cancelled")

308

309

case AUTH_ERROR =>

310

BadRequest("Authentication failed")

311

312

case NO_SERVER =>

313

BadRequest("Invalid OpenID provider")

314

315

case NETWORK_ERROR =>

316

InternalServerError("Network error during authentication")

317

318

case ex: OpenIDError =>

319

BadRequest(s"OpenID error: ${ex.getClass.getSimpleName}")

320

321

case ex =>

322

InternalServerError(s"Unexpected error: ${ex.getMessage}")

323

}

324

```

325

326

### Attribute Exchange

327

328

Request user attributes from OpenID providers supporting attribute exchange.

329

330

**Common Attribute Exchange Parameters:**

331

332

```scala

333

// Standard AX schema URLs

334

val axAttributes = Map(

335

"email" -> "http://axschema.org/contact/email",

336

"firstname" -> "http://axschema.org/namePerson/first",

337

"lastname" -> "http://axschema.org/namePerson/last",

338

"fullname" -> "http://axschema.org/namePerson",

339

"nickname" -> "http://axschema.org/namePerson/friendly",

340

"country" -> "http://axschema.org/contact/country/home",

341

"language" -> "http://axschema.org/pref/language",

342

"timezone" -> "http://axschema.org/pref/timezone"

343

)

344

345

// Request required attributes

346

val axRequired = Seq(

347

"email" -> axAttributes("email"),

348

"name" -> axAttributes("fullname")

349

)

350

351

// Request optional attributes

352

val axOptional = Seq(

353

"nickname" -> axAttributes("nickname"),

354

"country" -> axAttributes("country")

355

)

356

357

OpenID.redirectURL(

358

openID = "https://www.google.com/accounts/o8/id",

359

callbackURL = callbackURL,

360

axRequired = axRequired,

361

axOptional = axOptional

362

)

363

```

364

365

### Provider-Specific Examples

366

367

**Google OpenID:**

368

369

```scala

370

def loginWithGoogle = Action.async { implicit request =>

371

val googleOpenId = "https://www.google.com/accounts/o8/id"

372

val callbackURL = routes.OpenIDController.googleCallback().absoluteURL()

373

374

val axRequired = Seq(

375

"email" -> "http://axschema.org/contact/email",

376

"firstname" -> "http://axschema.org/namePerson/first",

377

"lastname" -> "http://axschema.org/namePerson/last"

378

)

379

380

OpenID.redirectURL(googleOpenId, callbackURL, axRequired).map { url =>

381

Redirect(url)

382

}

383

}

384

```

385

386

**Yahoo OpenID:**

387

388

```scala

389

def loginWithYahoo = Action.async { implicit request =>

390

val yahooOpenId = "https://me.yahoo.com"

391

val callbackURL = routes.OpenIDController.yahooCallback().absoluteURL()

392

393

val axRequired = Seq(

394

"email" -> "http://axschema.org/contact/email",

395

"fullname" -> "http://axschema.org/namePerson"

396

)

397

398

OpenID.redirectURL(yahooOpenId, callbackURL, axRequired).map { url =>

399

Redirect(url)

400

}

401

}

402

```

403

404

**Custom OpenID Provider:**

405

406

```scala

407

def loginWithCustom(openIdUrl: String) = Action.async { implicit request =>

408

val callbackURL = routes.OpenIDController.customCallback().absoluteURL()

409

410

// Validate OpenID URL format

411

if (!openIdUrl.startsWith("http://") && !openIdUrl.startsWith("https://")) {

412

Future.successful(BadRequest("Invalid OpenID URL"))

413

} else {

414

OpenID.redirectURL(openIdUrl, callbackURL).map { url =>

415

Redirect(url)

416

}.recover {

417

case ex =>

418

BadRequest(s"Invalid OpenID provider: ${ex.getMessage}")

419

}

420

}

421

}

422

```

423

424

### Complete OpenID Integration

425

426

```scala

427

import play.api.libs.openid._

428

import play.api.mvc._

429

import play.api.data._

430

import play.api.data.Forms._

431

import scala.concurrent.Future

432

import scala.concurrent.ExecutionContext.Implicits.global

433

434

class OpenIDAuthController @Inject()(cc: ControllerComponents)

435

extends AbstractController(cc) {

436

437

val openIdForm = Form(

438

"openid_identifier" -> nonEmptyText

439

)

440

441

def loginForm = Action { implicit request =>

442

Ok(views.html.openid.login(openIdForm))

443

}

444

445

def authenticate = Action.async { implicit request =>

446

openIdForm.bindFromRequest.fold(

447

formWithErrors => {

448

Future.successful(BadRequest(views.html.openid.login(formWithErrors)))

449

},

450

openIdIdentifier => {

451

val callbackURL = routes.OpenIDAuthController.callback().absoluteURL()

452

453

// Standard attributes to request

454

val axRequired = Seq(

455

"email" -> "http://axschema.org/contact/email"

456

)

457

458

val axOptional = Seq(

459

"firstname" -> "http://axschema.org/namePerson/first",

460

"lastname" -> "http://axschema.org/namePerson/last",

461

"fullname" -> "http://axschema.org/namePerson"

462

)

463

464

OpenID.redirectURL(

465

openID = openIdIdentifier,

466

callbackURL = callbackURL,

467

axRequired = axRequired,

468

axOptional = axOptional,

469

realm = Some(request.host)

470

).map { redirectUrl =>

471

Redirect(redirectUrl)

472

}.recover {

473

case ex =>

474

BadRequest(views.html.openid.login(

475

openIdForm.withGlobalError(s"OpenID error: ${ex.getMessage}")

476

))

477

}

478

}

479

)

480

}

481

482

def callback = Action.async { implicit request =>

483

OpenID.verifiedId.map { userInfo =>

484

// Extract user information

485

val openId = userInfo.id

486

val email = userInfo.attributes.get("email").getOrElse("")

487

val name = userInfo.attributes.get("fullname")

488

.orElse(userInfo.attributes.get("firstname").map(_ + " " + userInfo.attributes.get("lastname").getOrElse("")))

489

.getOrElse("Unknown")

490

491

// In a real app, you would save this to your user database

492

// For this example, we'll just store in session

493

Redirect(routes.Application.profile())

494

.withSession(

495

"openid" -> openId,

496

"email" -> email,

497

"name" -> name

498

)

499

.flashing("success" -> "Successfully logged in with OpenID")

500

501

}.recover {

502

case Errors.AUTH_CANCEL =>

503

Redirect(routes.OpenIDAuthController.loginForm())

504

.flashing("warning" -> "Authentication was cancelled")

505

506

case Errors.AUTH_ERROR =>

507

Redirect(routes.OpenIDAuthController.loginForm())

508

.flashing("error" -> "Authentication failed")

509

510

case ex =>

511

Redirect(routes.OpenIDAuthController.loginForm())

512

.flashing("error" -> s"Authentication error: ${ex.getMessage}")

513

}

514

}

515

516

def logout = Action { implicit request =>

517

Redirect(routes.Application.index())

518

.withNewSession

519

.flashing("success" -> "You have been logged out")

520

}

521

}