pennersr / django-allauth

Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication.
https://allauth.org
MIT License
9k stars 2.97k forks source link

Headless login with provider give cors error #3815

Closed Andrioden closed 2 weeks ago

Andrioden commented 2 weeks ago

My use case is that I want to use allauth to help me authenticate with Discord and Google, i dont want my own users stored locally.

Previously i had this working using a form, but seeing i could simplify this with headless, i went for it.

HTML

<v-btn @click="login()" size="x-large" class="d-flex w-100">{{ type }}</v-btn>

JS

login: async function() {
    await fetch("_allauth/browser/v1/auth/provider/redirect", {
        method: "POST",
        headers: {
            "X-CSRFToken": Cookies.get("csrftoken"),
            "Content-Type": "application/x-www-form-urlencoded"
        },
        body: new URLSearchParams({
            provider: this.type,
            callback_url: "/account/logged-in/",
            process: "login"
        })
    })
}

settings.py (relevant stuff)

ALLOWED_HOSTS = ["*"]

HEADLESS_FRONTEND_URLS = {
    "account_confirm_email": "https://app.project.org/account/verify-email/{key}",
    "account_reset_password_from_key": "https://app.org/account/password/reset/key/{key}",
    "account_signup": "https://app.org/account/signup",
    "socialaccount_login_error": "/ops-error",
}

AUTHENTICATION_BACKENDS = [
    # Needed to log in by username in Django admin, regardless of `allauth`
    "django.contrib.auth.backends.ModelBackend",
    # `allauth` specific authentication methods, such as login by e-mail
    "allauth.account.auth_backends.AuthenticationBackend",
]

SOCIALACCOUNT_PROVIDERS = {
    "discord": {
        "APP": config.oauth_discord,
    },
    "google": {
        "APP": config.oauth_google,
        "SCOPE": [
            "profile",
            "email",
        ],
        "AUTH_PARAMS": {
            "access_type": "online",
        },
        "OAUTH_PKCE_ENABLED": True,
    },
}

Here is what happens

  1. Click button in web client, allauth api is called - OK
  2. allauth api responds with redirect - OK
  3. Discord redirect OPTIONS and GET - FAIL

image

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://discord.com/api/oauth2/authorize?client_id=1054551832501960765&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Faccount%2Fdiscord%2Flogin%2Fcallback%2F&scope=identify+email&response_type=code&state=O0vEMIUD4X5B0wdI. (Reason: header ‘x-csrftoken’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://discord.com/api/oauth2/authorize?client_id=1054551832501960765&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Faccount%2Fdiscord%2Flogin%2Fcallback%2F&scope=identify+email&response_type=code&state=O0vEMIUD4X5B0wdI. (Reason: CORS request did not succeed). Status code: (null).

image

image

Andrioden commented 2 weeks ago

Bonus question: Why do i need to send X-CSRFToken?

"X-CSRFToken": Cookies.get("csrftoken"),

Without django logs Forbidden (CSRF token missing.): /_allauth/browser/v1/auth/provider/redirect. I would image allauth could take care of all that?

joonhyungshin commented 2 weeks ago

You do not need to send X-CSRFToken to pass the CSRF protection. Instead, you could attach the token to the request body.

login: async function() {
    await fetch("_allauth/browser/v1/auth/provider/redirect", {
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        body: new URLSearchParams({
            provider: this.type,
            callback_url: "/account/logged-in/",
            process: "login",
            csrfmiddlewaretoken: Cookies.get("csrftoken")
        })
    })
}

Since Discord disallows that header, try this option. I think it should work.

See for example, the demo code.

pennersr commented 2 weeks ago

This part from the docs is important:

As calling this endpoint results in a user facing redirect (302), this call is only available in a browser, and must be called in a synchronous (non-XHR) manner.

You are using fetch -- just use a regular form POST (as in done in the demo code link above).

Andrioden commented 2 weeks ago

This part from the docs is important:

As calling this endpoint results in a user facing redirect (302), this call is only available in a browser, and must be called in a synchronous (non-XHR) manner.

You are using fetch -- just use a regular form POST (as in done in the demo code link above).

Thank you, the postForm worked. Yeah sorry, I read that documentation, but i didnt understand what non-XHR manner meant and didnt think to check it up.