com.gu.identity.play.OktaPlayAuthService.scala Maven / Gradle / Ivy
package com.gu.identity.play
import cats.effect.IO
import com.gu.identity.auth._
import play.api.mvc.RequestHeader
import scala.concurrent.ExecutionContext
// Class that builds upon functionality of OktaAuthService
// to make user authentication convenient in the context of a Play application.
// It handles extracting the user credentials from a Play request before authenticating them
// using the underlying OktaAuthServer.
class OktaPlayAuthService(oktaAuthService: OktaAuthService) {
def oktaTokenFromAuthorizationHeader(requestHeader: RequestHeader): Either[ValidationError, String] =
Helpers.fetchBearerTokenFromAuthHeader(requestHeader.headers.get)
/** Returns the set of access claims on the given access token. Access tokens contain a set of claims that can be
* used by API endpoints to carry out their processes: an example is a legacy Identity ID. The access token takes the
* form of a JWT and when it's decoded it contains a Json access claim sub-object. We return this sub-object as a map
* rather than a case class for maximum flexibility. The number of claims on the token will vary depending on the
* access scopes that the token has been granted.
*
* This is a fast and lightweight method that makes no calls to the auth server. It's entirely dependent on the
* access token given.
*
* If you are concerned that the token might be invalid, have been revoked or have expired, this method might not
* be the most suitable.
*/
def getParsedClaimsLocallyFromRequest[A <: AccessClaims](
requestHeader: RequestHeader,
requiredScopes: List[AccessScope],
)(implicit accessClaimsParser: AccessClaimsParser[A]): Either[ValidationError, A] =
for {
token <- oktaTokenFromAuthorizationHeader(requestHeader)
claims <- oktaAuthService.validateAccessTokenLocally(AccessToken(token), requiredScopes)
} yield claims
/** Validates the given access token on the auth server that issued it and returns the set of access claims on the
* given access token and also the identity claims returned by the auth server. The access claims will be those that
* were on the token when it was issued so there is a possibility they will be out of date.
*
* There might be common values between the access claims and the identity claims but they won't necessarily have
* the same names. They might also have different semantics so treat the identity claims with care and don't assume
* that they can be used in place of particular access claims.
*
* If the token is invalid, has been revoked or has expired the response will fail at runtime with an
* [[OktaValidationException]].
*
* This method makes a call on the auth server so only use it when necessary! If it's used too
* often, the call will be rate-limited and performance will suffer.
*/
def getParsedClaimsFromServerValidatedAccessToken[I <: IdentityClaims, A <: AccessClaims](
requestHeader: RequestHeader,
requiredScopes: List[AccessScope],
)(implicit
userInfoResponseParser: UserInfoResponseParser[I],
accessClaimsParser: AccessClaimsParser[A]
): IO[CombinedClaims[I, A]] = {
for {
token <- IO.fromEither(oktaTokenFromAuthorizationHeader(requestHeader).left.map(OktaValidationException))
claims <- oktaAuthService.validateAccessTokenServerSide(AccessToken(token), requiredScopes)
} yield claims
}
}
object OktaPlayAuthService {
def apply(config: OktaTokenValidationConfig)(implicit ec: ExecutionContext): OktaPlayAuthService = {
val oktaAuthService = OktaAuthService(config)
new OktaPlayAuthService(oktaAuthService)
}
}