async def validate_userinfo_params(request: Request) -> Dict[str, Any]:
    provider = get_provider(request)

    if request.method == "POST":
        req_dict = request.form
    else:
        req_dict = request.args

    access_token = None
    if "authorization" in request.headers:
        hdr = request.headers["authorization"]
        if "Bearer" in hdr:
            access_token = hdr.split()[-1]
        else:
            raise NotImplementedError(hdr)

    elif "access_token" in req_dict:
        access_token = req_dict.get("access_token")

    if not access_token:
        raise TokenError("invalid_grant")

    token = await provider.tokens.get_token_by_access_token(access_token)
    if not token:
        raise TokenError("invalid_grant")

    result = {
        "token": token,
    }

    return result
async def well_known_finger_handler(
        request: sanic.request.Request) -> sanic.response.BaseHTTPResponse:
    provider = get_provider(request)
    scheme = get_scheme(request)

    resource = request.args.get("resource")
    rel = request.args.get("rel")
    finger_url = request.app.url_for("well_known_finger_handler",
                                     _scheme=scheme,
                                     _external=True,
                                     _server=request.host)
    issuer = "{0}://{1}".format(scheme, request.host)

    logger.info("finger for resource: {0} rel: {1}".format(resource, rel))

    try:
        resp = provider.handle_finger(resource, rel, issuer, finger_url)
        return sanic.response.json(
            resp,
            content_type="application/jrd+json",
            headers={"Access-Control-Allow-Origin": "*"})
    except Exception as err:
        logger.exception("Caught error whilst handling finger url",
                         exc_info=err)

    return sanic.response.HTTPResponse(status=500)
async def create_refresh_response_dic(
        request: sanic.request.Request, params: Dict[str,
                                                     Any]) -> Dict[str, Any]:
    provider = get_provider(request)
    # See https://tools.ietf.org/html/rfc6749#section-6

    scope_param = params["scope"]
    scope = scope_param if scope_param else params["token_obj"]["scope"]
    unauthorized_scopes = set(scope) - set(params["token_obj"]["scope"])
    if unauthorized_scopes:
        raise TokenError("invalid_scope")

    user = await provider.users.get_user_by_username(
        params["token_obj"]["user"])
    client = params["client"]

    token = provider.tokens.create_token(
        user=user,
        client=client,
        scope=scope,
        auth_time=params["token_obj"]["auth_time"],
        specific_claims=params["specific_claims"],
        expire_delta=provider.token_expire_time,
    )

    scheme = get_scheme(request)
    issuer = "{0}://{1}".format(scheme, request.host)

    # If the Token has an id_token it's an Authentication request.
    if params["token_obj"]["id_token"]:
        id_token_dic = provider.tokens.create_id_token(
            user=user,
            auth_time=token["auth_time"],
            issuer=issuer,
            client=client,
            nonce=None,
            expire_delta=provider.token_expire_time,
            at_hash=token["at_hash"],
            scope=token["scope"],
            specific_claims=token["specific_claims"],
        )
    else:
        id_token_dic = {}

    token["id_token"] = id_token_dic
    await provider.tokens.save_token(token)
    await provider.tokens.delete_token_by_access_token(
        params["token_obj"]["access_token"])

    id_token = await client.sign(id_token_dic, jwk_set=provider.jwk_set)

    dic = {
        "access_token": token["access_token"],
        "refresh_token": token["refresh_token"],
        "token_type": "bearer",
        "expires_in": provider.token_expire_time,
        "id_token": id_token,
    }

    return dic
async def create_code_response_dic(request: sanic.request.Request,
                                   params: Dict[str, Any]) -> Dict[str, Any]:
    provider = get_provider(request)
    # See https://tools.ietf.org/html/rfc6749#section-4.1

    user = await provider.users.get_user_by_username(params["code_obj"]["user"]
                                                     )
    client = params["client"]

    token = provider.tokens.create_token(
        user=user,
        client=client,
        auth_time=params["code_obj"]["auth_time"],
        scope=params["code_obj"]["scope"],
        expire_delta=provider.token_expire_time,
        specific_claims=params["code_obj"]["specific_claims"],
        code=params["code"],
    )

    scheme = get_scheme(request)
    issuer = "{0}://{1}".format(scheme, request.host)

    id_token_dic = provider.tokens.create_id_token(
        user=user,
        auth_time=token["auth_time"],
        client=client,
        issuer=issuer,
        expire_delta=provider.token_expire_time,
        nonce=params["code_obj"]["nonce"],
        at_hash=token["at_hash"],
        scope=token["scope"],
        specific_claims=token["specific_claims"],
    )

    token["id_token"] = id_token_dic
    await provider.tokens.save_token(token)
    await provider.codes.mark_used_by_id(params["code"])

    id_token = await client.sign(id_token_dic, jwk_set=provider.jwk_set)

    dic = {
        "access_token": token["access_token"],
        "refresh_token": token["refresh_token"],
        "token_type": "bearer",
        "expires_in": provider.token_expire_time,
        "id_token": id_token,
    }

    return dic
async def jwk_handler(
        request: sanic.request.Request) -> sanic.response.BaseHTTPResponse:
    headers = {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, OPTIONS"
    }

    if request.method == "OPTIONS":
        return sanic.response.HTTPResponse(headers=headers)

    provider = get_provider(request)
    keys = []

    for key in provider.jwk_set:
        keys.append(key._public_params())  # so we dont get json strings

    async for client in provider.clients.all():
        for key in client.jwk:
            keys.append(key._public_params())  # so we dont get json strings

    return sanic.response.json({"keys": keys})
async def client_register_handler(
        request: sanic.request.Request) -> sanic.response.BaseHTTPResponse:
    provider = get_provider(request)
    scheme = get_scheme(request)

    if "client_id" in request.args:
        # Client Read, check auth header
        try:
            token = request.headers["Authorization"].split("Bearer ")[-1]
            client = await provider.clients.get_client_by_access_token(token)
        except Exception:
            return sanic.response.text(
                body="",
                status=403,
                headers={"WWW-Authenticate": 'Bearer error="invalid_token"'})

        result = {
            "client_id": client.id,
            "client_secret": client.secret,
            "client_secret_expires_at": client.expires_at,
            "subject_type": client.type,
            "application_type": client.application_type,
            "response_types": client.response_types,
            "redirect_uris": client.callback_urls,
            "grant_types": client.grant_types,
            "contacts": client.contacts,
            "jwks_uri": client.jwks_url,
            "post_logout_redirect_uris": client.post_logout_redirect_urls,
            "request_uris": client.request_urls,
            # 'registration_client_uri': request.app.url_for('client_register_handler', _scheme=scheme,
            #                                                _external=True, _server=request.host,
            #                                                client_id=client_id),
            # 'registration_access_token': client.access_token,
        }
        if client.sector_identifier_uri:
            result["sector_identifier_uri"] = client.sector_identifier_uri
        if client.jwt_algo:
            result["id_token_signed_response_alg"] = client.jwt_algo
        if client.userinfo_signed_response_alg:
            result[
                "userinfo_signed_response_alg"] = client.userinfo_signed_response_alg

        status = 201

    else:
        if not provider.open_client_registration and not await provider.clients.auth_client_registration(
                request):
            return sanic.response.HTTPResponse(status=403)

        if not request.json or "redirect_uris" not in request.json:
            logger.warning("Did not provide any JSON or redirect_uris")
            result = {
                "error": "invalid_client_metadata",
                "error_description": "Invalid metadata"
            }
            return sanic.response.json(result, status=400)

        client_id = uuid.uuid4().hex[:16]
        client_name = request.json.get("client_name", client_id)
        client_secret = uuid.uuid4().hex
        client_secret_expires_at = 1577858400  # 1st jan 2020

        application_type = request.json.get("application_type")
        response_types = request.json.get("response_types",
                                          frozenset(["code"]))
        scopes = request.json.get("scope", ["openid"])
        redirect_uris = request.json.get("redirect_uris", [])
        grant_types = request.json.get("grant_types")
        contacts = request.json.get("contacts")
        jwks_uri = request.json.get("jwks_uri")
        jwks = request.json.get("jwks")
        post_logout_redirect_uris = request.json.get(
            "post_logout_redirect_uris")
        request_uris = request.json.get("request_uris")
        prompt = request.json.get("prompt",
                                  frozenset(["none", "login", "consent"]))
        sector_identifier_uri = request.json.get("sector_identifier_uri")
        subject_type = request.json.get("subject_type", "public")
        logo_uri = request.json.get("logo_uri")
        policy_uri = request.json.get("policy_uri")
        tos_uri = request.json.get("tos_uri")

        if isinstance(scopes, str):
            scopes = set(scopes.split())
        scopes.add("openid")
        # TODO request_object_signing_alg
        #

        require_consent = request.json.get("require_consent") is True
        reuse_consent = request.json.get("reuse_consent") is True
        id_token_signed_response_alg = request.json.get(
            "id_token_signed_response_alg")
        userinfo_signed_response_alg = request.json.get(
            "userinfo_signed_response_alg")
        userinfo_encrypted_response_alg = request.json.get(
            "userinfo_encrypted_response_alg")
        userinfo_encrypted_response_enc = request.json.get(
            "userinfo_encrypted_response_enc")

        for url in redirect_uris:
            if "#" in url:
                # NO BAD, shouldnt have fragments in url
                result = {
                    "error": "invalid_redirect_uri",
                    "error_description": "Bad redirect uri {0}".format(url)
                }
                return sanic.response.json(result, status=400)

        # Validate sector_identifier_uri, must contain a superset of redirect_uris
        if sector_identifier_uri:
            try:
                async with aiohttp.ClientSession() as session:
                    logger.info("Getting Sector Identifier URI {0}".format(
                        sector_identifier_uri))
                    async with session.get(sector_identifier_uri) as resp:
                        sector_json = await resp.json()
                        if not isinstance(sector_json, list):
                            raise Exception(
                                "sector identifier json is not a list")

                        invalid_uris = set(redirect_uris) - set(sector_json)
                        if invalid_uris:
                            raise Exception(
                                "Invalid redirect uris: {0}".format(
                                    invalid_uris))

            except Exception as err:
                logger.warning(
                    "Failed to get sector identifier uri: {0}".format(err))
                result = {
                    "error":
                    "invalid_client_metadata",
                    "error_description":
                    "Failed to validate sector identifier uri, {0}".format(
                        err),
                }
                return sanic.response.json(result, status=400)

        success, data = await provider.clients.add_client(
            id_=client_id,
            name=client_name,
            type_=subject_type,
            secret=client_secret,
            scopes=scopes,
            callback_urls=redirect_uris,
            require_consent=require_consent,
            reuse_consent=reuse_consent,
            response_types=response_types,
            application_type=application_type,
            contacts=contacts,
            expires_at=client_secret_expires_at,
            grant_types=grant_types,
            jwks_url=jwks_uri,
            jwt_algo=id_token_signed_response_alg,
            prompts=prompt,
            post_logout_redirect_urls=post_logout_redirect_uris,
            request_urls=request_uris,
            sector_identifier_uri=sector_identifier_uri,
            userinfo_signed_response_alg=userinfo_signed_response_alg,
            userinfo_encrypted_response_alg=userinfo_encrypted_response_alg,
            userinfo_encrypted_response_enc=userinfo_encrypted_response_enc,
            logo_uri=logo_uri,
            policy_uri=policy_uri,
            tos_uri=tos_uri,
            jwks=jwks,
        )

        if success:
            client: Client = data

            result = {
                "client_id":
                client_id,
                "client_secret":
                client_secret,
                "client_secret_expires_at":
                client_secret_expires_at,
                "subject_type":
                "confidential",
                "application_type":
                application_type,
                "response_types":
                response_types,
                "redirect_uris":
                redirect_uris,
                "grant_types":
                grant_types,
                "contacts":
                contacts,
                "jwks_uri":
                jwks_uri,
                "post_logout_redirect_uris":
                post_logout_redirect_uris,
                "request_uris":
                request_uris,
                "registration_client_uri":
                request.app.url_for("client_register_handler",
                                    _scheme=scheme,
                                    _external=True,
                                    _server=request.host,
                                    client_id=client_id),
                "registration_access_token":
                client.access_token,
                # 'token_endpoint_auth_method': 'client_secret_basic'
            }
            if sector_identifier_uri:
                result["sector_identifier_uri"] = sector_identifier_uri
            if id_token_signed_response_alg:
                result[
                    "id_token_signed_response_alg"] = id_token_signed_response_alg
            if logo_uri:
                result["logo_uri"] = logo_uri
            if tos_uri:
                result["tos_uri"] = tos_uri
            if policy_uri:
                result["policy_uri"] = policy_uri

            status = 201
        else:
            result = {
                "error": "invalid_client_metadata",
                "error_description": data
            }
            status = 500

    return sanic.response.json(result,
                               headers={
                                   "Cache-Control": "no-store",
                                   "Pragma": "no-cache"
                               },
                               status=status)
async def userinfo_handler(
        request: sanic.request.Request) -> sanic.response.BaseHTTPResponse:
    headers = {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Authorization",
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
    }

    if request.method == "OPTIONS":
        return sanic.response.HTTPResponse(headers=headers)

    provider = get_provider(request)

    try:
        params = await validate_userinfo_params(request)
        token = params["token"]

        client = await provider.clients.get_client_by_id(token["client"])

        if token.get("specific_claims", {}) is None:
            specific_claims = {}
        else:
            specific_claims = token["specific_claims"]

        specific_claims = specific_claims.get("userinfo", {}).keys()
        claims = await provider.users.get_claims_for_user_by_scope(
            token["user"], token["scope"], specific_claims)

        result = {"sub": token["user"]}
        result.update(claims)

        if client.userinfo_signed_response_alg:
            # Sign response
            result = await client.jws_sign(
                result,
                algo=client.userinfo_signed_response_alg,
                jwk_set=provider.jwk_set)

        if client.userinfo_encrypted_response_alg:
            # Encrypt response
            result = await client.jws_encrypt(
                result,
                alg=client.userinfo_encrypted_response_alg,
                enc=client.userinfo_encrypted_response_enc,
                jwk_set=None,
            )

        if isinstance(result, str):
            headers.update({
                "Cache-Control": "no-store",
                "Pragma": "no-cache",
                "Content-Type": "application/jwt"
            })

            # If we no longer have plain json, its most likely a JWT of sorts
            return sanic.response.HTTPResponse(body=result, headers=headers)
        else:
            headers.update({"Cache-Control": "no-store", "Pragma": "no-cache"})

            return sanic.response.json(result, headers=headers)

    except TokenError as error:
        return sanic.response.json(error.create_dict(),
                                   status=400,
                                   headers=headers)
async def create_authorize_response_params(
        request: sanic.request.Request, params: Dict[str, Any],
        user: Dict[str, Any]) -> Tuple[dict, dict]:
    provider = get_provider(request)
    client = params["client"]

    uri = urlsplit(params["redirect_uri"])
    query_params = parse_qs(uri.query)
    query_fragment = {}

    try:
        if params["grant_type"] in ("authorization_code", "hybrid"):
            code = await provider.codes.create_code(
                client=client,
                user=user,
                scopes=params["scopes"],
                code_expire=int(provider.code_expire_time),
                nonce=params["nonce"],
                code_challenge=params["code_challenge"],
                code_challenge_method=params["code_challenge_method"],
                specific_claims=params["specific_claims"],
            )

        if params["grant_type"] == "authorization_code":
            # noinspection PyUnboundLocalVariable
            query_params["code"] = code["code"]
            query_params["state"] = params["state"]

        elif params["grant_type"] in ["implicit", "hybrid"]:
            token = provider.tokens.create_token(
                user=user,
                client=client,
                auth_time=user["auth_time"],
                scope=params["scopes"],
                expire_delta=provider.token_expire_time,
                specific_claims=params["specific_claims"],
            )

            # Check if response_type must include access_token in the response.
            if params["response_type"] in ("id_token token", "token",
                                           "code token",
                                           "code id_token token"):
                query_fragment["access_token"] = token["access_token"]

            # We don't need id_token if it's an OAuth2 request.
            if "openid" in params["scopes"]:
                scheme = get_scheme(request)
                issuer = "{0}://{1}".format(scheme, request.host)

                kwargs = {
                    "auth_time": token["auth_time"],
                    "user": user,
                    "client": client,
                    "issuer": issuer,
                    "expire_delta": provider.token_expire_time,
                    "nonce": params["nonce"],
                    "at_hash": token["at_hash"],
                    "scope": params["scopes"],
                    "specific_claims": params["specific_claims"],
                }
                # Include at_hash when access_token is being returned.
                if "access_token" in query_fragment:
                    kwargs["at_hash"] = token["at_hash"]
                id_token_dic = provider.tokens.create_id_token(**kwargs)

                # Check if response_type must include id_token in the response.
                if params["response_type"] in ("id_token", "id_token token",
                                               "code id_token",
                                               "code id_token token"):

                    query_fragment["id_token"] = await client.sign(
                        id_token_dic, jwk_set=provider.jwk_set)
            else:
                id_token_dic = {}

            # Store the token.
            token["id_token"] = id_token_dic
            await provider.tokens.save_token(token)

            # Code parameter must be present if it's Hybrid Flow.
            if params["grant_type"] == "hybrid":
                # noinspection PyUnboundLocalVariable
                query_fragment["code"] = code["code"]

            query_fragment["token_type"] = "bearer"
            query_fragment["expires_in"] = provider.token_expire_time
            query_fragment["state"] = params["state"]

    except Exception as err:
        logger.exception("Failed whilst creating authorize response",
                         exc_info=err)
        raise AuthorizeError(params["redirect_uri"], "server_error",
                             params["grant_type"])

    query_params = {key: value for key, value in query_params.items() if value}
    query_fragment = {
        key: value
        for key, value in query_fragment.items() if value
    }

    return query_params, query_fragment
async def authorize_handler(
        request: sanic.request.Request) -> sanic.response.BaseHTTPResponse:
    provider = get_provider(request)

    # TODO split out based on response_type

    try:
        params = await validate_authorize_params(request, provider)

        if await provider.users.is_authenticated(request):
            user = await provider.users.get_user(request)

            if "login" in params["prompt"]:
                if "none" in params["prompt"]:
                    # If login and none in prompt arg
                    logger.warning("login prompt along with none prompt")
                    raise AuthorizeError(params["redirect_uri"],
                                         "login_required",
                                         params["grant_type"])
                else:
                    # If login is in prompt arg
                    request.ctx.session.clear()
                    next_page = strip_prompt_login(get_request_url(request))
                    return redirect(
                        request.app.url_for(provider.login_function_name,
                                            next=next_page))

            if "select_account" in params["prompt"]:
                if "none" in params["prompt"]:
                    logger.warning(
                        "select_account prompt along with none prompt")
                    raise AuthorizeError(params["redirect_uri"],
                                         "account_selection_required",
                                         params["grant_type"])
                else:
                    request.ctx.session.clear()
                    return redirect(
                        request.app.url_for(provider.login_function_name,
                                            next=get_request_url(request)))

            if {
                    "none", "consent"
            } <= params["prompt"]:  # Tests if both none and consent in prompt
                logger.warning("consent prompt along with none prompt")
                raise AuthorizeError(params["redirect_uri"],
                                     "consent_required", params["grant_type"])

            implicit_flow_resp_types = {"id_token", "id_token token"}
            allow_skipping_consent = (
                params["client"].type in ("public", "pairwise")
                or params["response_type"] in implicit_flow_resp_types)

            if not params[
                    "client"].require_consent and allow_skipping_consent and "consent" not in params[
                        "prompt"]:
                # If you dont require consent, and consent is allowed to be skipped
                # (aka type=confidential), and consent hasn't been requested
                query_params, query_fragment = await create_authorize_response_params(
                    request, params, user)

                if params["response_mode"] == "form_post":
                    # We've been requested to auto-post form data
                    logger.info(
                        "skipped consent, doing form-autosubmit for {0}".
                        format(params["client"].name))
                    return await request.app.extensions["jinja2"].render_async(
                        provider.autosubmit_html,
                        request,
                        form_url=params["redirect_uri"],
                        query_params=query_params,
                        query_fragment=query_fragment,
                    )
                else:
                    # Standard 302 redirect
                    logger.info(
                        "skipped consent, doing 302 redirect for {0}".format(
                            params["client"].name))
                    return redirect(
                        create_authorize_response_uri(params["redirect_uri"],
                                                      query_params,
                                                      query_fragment))

            # We require consent
            if params["client"].reuse_consent:
                # Allowing prior consent to be reused
                if user["consent"] and allow_skipping_consent and "consent" not in params[
                        "prompt"]:
                    # If user has already given consent, and consent not requested
                    query_params, query_fragment = await create_authorize_response_params(
                        request, params, user)

                    if params["response_mode"] == "form_post":
                        # We've been requested to auto-post form data
                        logger.info(
                            "reusing consent, doing form-autosubmit for {0}".
                            format(params["client"].name))
                        return await request.app.extensions[
                            "jinja2"].render_async(
                                provider.autosubmit_html,
                                request,
                                form_url=params["redirect_uri"],
                                query_params=query_params,
                                query_fragment=query_fragment,
                            )
                    else:
                        # Standard 302 redirect
                        logger.info(
                            "reusing consent, doing 302 redirect for {0}".
                            format(params["client"].name))
                        return redirect(
                            create_authorize_response_uri(
                                params["redirect_uri"], query_params,
                                query_fragment))

            if "none" in params["prompt"]:
                # Return consent_required
                logger.info("none prompt, giving up")
                raise AuthorizeError(params["redirect_uri"],
                                     "consent_required", params["grant_type"])

            # Generate hidden inputs for the form.
            hidden_params = {
                "client_id": params["client"].id,
                "redirect_uri": params["redirect_uri"],
                "grant_type": params["grant_type"],
                "response_type": params["response_type"],
                "scope": params["scopes"],
                "state": params["state"],
                "nonce": params["nonce"],
                "prompt": " ".join(list(params["prompt"])),
            }
            if params["code_challenge"]:
                hidden_params["code_challenge"] = params["code_challenge"]
                hidden_params["code_challenge_method"] = params[
                    "code_challenge_method"]
            if params["response_mode"]:
                hidden_params["response_mode"] = params["response_mode"]
            if params["max_age"]:
                hidden_params["max_age"] = params["max_age"]
            hidden_inputs = await request.app.extensions[
                "jinja2"].render_string_async(provider.hidden_inputs_html,
                                              request,
                                              params=hidden_params)

            # Remove `openid` from scope list since we don't need to print it.
            try:
                params["scopes"].remove("openid")
            except ValueError:
                pass

            context = {
                "client_name": params["client"].name,
                "form_url": request.path,
                "hidden_inputs": hidden_inputs,
                "scopes": params["scopes"],
            }

            # Show authorize html page #TODO allow it to be customised
            logger.info("showing consent for {0}".format(
                params["client"].name))
            return await request.app.extensions["jinja2"].render_async(
                provider.authorize_html, request, **context)

        else:
            # Not logged in
            if "none" in params["prompt"]:
                # Cant prompt, raise error
                logger.warning("Not logged in, prompt=none, error")
                raise AuthorizeError(params["redirect_uri"], "login_required",
                                     params["grant_type"])

            logger.warning("Not logged in, redirecting to login page")

            if "login" in params["prompt"]:
                # Can prompt, redirect them to login page
                next_page = strip_prompt_login(get_request_url(request))
                return redirect(
                    request.app.url_for(provider.login_function_name,
                                        next=next_page))

            # Nothing in prompt, so default to redirecting to login page
            return redirect(
                request.app.url_for(provider.login_function_name,
                                    next=get_request_url(request)))

    except (ClientIdError, RedirectUriError) as err:

        context = {"error": err.error, "description": err.description}
        return await request.app.extensions["jinja2"].render_async(
            provider.error_html, request, **context)

    except AuthorizeError as err:

        if request.method == "POST":
            req_dict = request.form
        else:
            req_dict = request.args

        state = req_dict.get("state")
        response_mode = req_dict.get("response_mode")

        if response_mode == "form_post":
            return await request.app.extensions["jinja2"].render_async(
                provider.autosubmit_html,
                request,
                form_url=err.redirect_uri,
                query_params={
                    "error": err.error,
                    "error_description": err.description
                },
                query_fragment={},
            )
        else:
            uri = err.create_uri(err.redirect_uri, state)
            return redirect(uri)
async def validate_token_params(
        request: sanic.request.Request) -> Dict[str, Any]:
    provider = get_provider(request)

    if request.method == "POST":
        req_dict = request.form
    else:
        req_dict = request.args

    client_assertion_type = req_dict.get("client_assertion_type")
    client_assertion = req_dict.get("client_assertion")

    if client_assertion_type and client_assertion_type == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer":
        header = jwt.get_unverified_header(client_assertion)
        audience = "{0}://{1}{2}".format(get_scheme(request), request.host,
                                         request.path)

        # TODO maintain central collection of client jwts
        if "kid" in header:
            # Asymetric signing
            temp_jwt_token = jwt.decode(client_assertion, verify=False)
            client = await provider.clients.get_client_by_id(
                temp_jwt_token["sub"])
            jwt_key = client.jwk.get_key(header.get("kid"))

            try:
                jwt.decode(client_assertion,
                           jwt_key.export_to_pem(),
                           algorithms=[header["alg"]],
                           audience=audience)
                # By it not erroring, its successfully verified
            except Exception as err:
                logger.exception("Invalid key id", exc_info=err)
                raise TokenError("invalid_client")

        else:
            # HMAC with client secret
            temp_jwt_token = jwt.decode(client_assertion, verify=False)
            client = await provider.clients.get_client_by_id(
                temp_jwt_token["sub"])

            try:
                jwt.decode(client_assertion,
                           client.secret,
                           algorithms=[header["alg"]],
                           audience=audience)
                # By it not erroring, its successfully verified
            except Exception as err:
                logger.exception("Invalid key id", exc_info=err)
                raise TokenError("invalid_client")

    else:
        client_id = req_dict.get("client_id")
        client_secret = req_dict.get("client_secret", "")
        if "authorization" in request.headers and not client_id:
            hdr = request.headers["authorization"]
            if "Basic" in hdr:
                client_id, client_secret = base64.b64decode(
                    hdr.split()[-1].encode()).decode().split(":")
            else:
                raise NotImplementedError(hdr)

        client = await provider.clients.get_client_by_id(client_id)
        if not client_id:
            raise TokenError("invalid_client")

    if not client:
        raise TokenError("invalid_client")

    specific_claims = req_dict.get("claims")
    if specific_claims:
        try:
            specific_claims = json.loads(specific_claims)
        except Exception as err:
            logger.exception("Failed to decode specific claims", exc_info=err)

    result = {
        "client": client,
        "grant_type": req_dict.get("grant_type", ""),
        "code": req_dict.get("code", ""),
        "state": req_dict.get("state", ""),
        "scope": req_dict.get("scope", ""),
        "redirect_uri": req_dict.get("redirect_uri", ""),
        "refresh_token": req_dict.get("refresh_token", ""),
        "code_verifier": req_dict.get("code_verifier"),
        "username": req_dict.get("username", ""),
        "password": req_dict.get("password", ""),
        "specific_claims": specific_claims,
    }

    # if client.type == 'confidential' and client_secret != client.secret:
    #     raise TokenError('invalid_client')

    if result["grant_type"] == "authorization_code":
        if result["redirect_uri"] not in client.callback_urls:
            raise TokenError("invalid_client")

        code = await provider.codes.get_by_id(result["code"])

        if not code:
            raise TokenError("invalid_grant")

        if code["used"]:
            await provider.tokens.delete_token_by_code(result["code"])
            raise TokenError("invalid_grant")

        if code["client"] != client.id:
            raise TokenError("invalid_grant")

        if result["code_verifier"]:
            if code["code_challenge_method"] == "S256":
                new_code_challenge = (base64.urlsafe_b64encode(
                    hashlib.sha256(result["code_verifier"].encode(
                        "ascii")).digest()).decode("utf-8").replace("=", ""))
            else:
                new_code_challenge = result["code_verifier"]

            if new_code_challenge != code["code_challenge"]:
                raise TokenError("invalid_grant")

        result["code_obj"] = code

    elif result["grant_type"] == "password":
        if not provider.allow_grant_type_password:
            raise TokenError("unsupported_grant_type")

        # TODO authenticate username/password
        # result['username'] result['password']
        user = False

        if not user:
            raise UserAuthError()

        result["user_obj"] = user

    elif result["grant_type"] == "client_credentials":
        # TODO not sure about this
        raise NotImplementedError()

    elif result["grant_type"] == "refresh_token":
        if not result["refresh_token"]:
            logger.warning("No refresh token")
            raise TokenError("invalid_grant")

        token = await provider.tokens.get_token_by_refresh_token(
            result["refresh_token"])
        if not token:
            raise TokenError("invalid_grant")

        result["token_obj"] = token

    return result