thibaud frere commited on
Commit
ea67b4c
·
1 Parent(s): 69b83cc
Files changed (2) hide show
  1. index.html +53 -37
  2. test.md +138 -0
index.html CHANGED
@@ -29,13 +29,9 @@
29
  <div id="status"></div>
30
  <pre id="userinfo" style="display:none"></pre>
31
  </div>
32
- <script type="module">
33
- // Import de la librairie HF pour une méthode plus simple et fiable
34
- import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "https://cdn.skypack.dev/@huggingface/hub";
35
-
36
- // Fallback pour les variables OAuth si on utilise la méthode manuelle
37
- const CLIENT_ID = window.OAUTH_CLIENT_ID || window.huggingface?.space?.oauth?.clientId;
38
- const CLIENT_SECRET = window.OAUTH_CLIENT_SECRET || window.huggingface?.space?.oauth?.clientSecret;
39
  const REDIRECT_URI = window.location.origin + window.location.pathname;
40
  const HF_OAUTH_URL = 'https://huggingface.co/oauth/authorize';
41
  const HF_TOKEN_URL = 'https://huggingface.co/oauth/token';
@@ -56,15 +52,14 @@
56
  document.getElementById('userinfo').textContent = '';
57
  }
58
 
59
- // Sign in button - utilise la méthode recommandée par HF
60
- document.getElementById('signin').onclick = async function () {
61
- try {
62
- const loginUrl = await oauthLoginUrl();
63
- window.location.href = loginUrl;
64
- } catch (error) {
65
- console.error('Erreur lors de la génération de l\'URL de login:', error);
66
- document.getElementById('status').textContent = 'Erreur: Impossible de générer l\'URL de connexion. Vérifiez la configuration OAuth.';
67
- }
68
  };
69
 
70
  // Sign out button
@@ -76,34 +71,55 @@
76
  window.history.replaceState({}, '', window.location.pathname);
77
  };
78
 
79
- // Handle OAuth callback - utilise la méthode recommandée par HF
80
  window.onload = async function () {
81
- try {
82
- // Utilise la fonction HF pour gérer le redirect OAuth
83
- const oauthResult = await oauthHandleRedirectIfPresent();
84
-
85
- if (oauthResult) {
86
- // L'utilisateur vient de se connecter
87
- console.log('OAuth result:', oauthResult);
88
- const userinfoStr = JSON.stringify(oauthResult.userInfo, null, 2);
89
- localStorage.setItem('hf_oauth_token', oauthResult.accessToken);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  localStorage.setItem('hf_oauth_userinfo', userinfoStr);
91
  showLoggedIn(userinfoStr);
92
  // Clean up URL
93
  window.history.replaceState({}, '', window.location.pathname);
94
  } else {
95
- // Vérifier si l'utilisateur est déjà connecté
96
- const token = localStorage.getItem('hf_oauth_token');
97
- const userinfo = localStorage.getItem('hf_oauth_userinfo');
98
- if (token && userinfo) {
99
- showLoggedIn(userinfo);
100
- } else {
101
- showLoggedOut();
102
- }
103
  }
104
- } catch (error) {
105
- console.error('Erreur OAuth:', error);
106
- document.getElementById('status').textContent = 'Erreur lors de la gestion OAuth: ' + error.message;
 
 
 
 
 
 
107
  showLoggedOut();
108
  }
109
  };
 
29
  <div id="status"></div>
30
  <pre id="userinfo" style="display:none"></pre>
31
  </div>
32
+ <script>
33
+ // Utiliser le client_id injecté par Hugging Face dans l'environnement du Space
34
+ const CLIENT_ID = window.huggingface?.variables?.OAUTH_CLIENT_ID;
 
 
 
 
35
  const REDIRECT_URI = window.location.origin + window.location.pathname;
36
  const HF_OAUTH_URL = 'https://huggingface.co/oauth/authorize';
37
  const HF_TOKEN_URL = 'https://huggingface.co/oauth/token';
 
52
  document.getElementById('userinfo').textContent = '';
53
  }
54
 
55
+ // Sign in button
56
+ document.getElementById('signin').onclick = function () {
57
+ const state = Math.random().toString(36).slice(2);
58
+ localStorage.setItem('hf_oauth_state', state);
59
+ const url = `${HF_OAUTH_URL}?client_id=${CLIENT_ID}` +
60
+ `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
61
+ `&response_type=code&scope=openid%20profile&state=${state}&prompt=consent`;
62
+ window.location = url;
 
63
  };
64
 
65
  // Sign out button
 
71
  window.history.replaceState({}, '', window.location.pathname);
72
  };
73
 
74
+ // Handle OAuth callback
75
  window.onload = async function () {
76
+ // If returning from OAuth redirect
77
+ const params = new URLSearchParams(window.location.search);
78
+ if (params.has('code') && params.has('state')) {
79
+ const state = params.get('state');
80
+ if (state !== localStorage.getItem('hf_oauth_state')) {
81
+ document.getElementById('status').textContent = 'Invalid state, possible CSRF detected.';
82
+ return;
83
+ }
84
+ const code = params.get('code');
85
+ const body = new URLSearchParams({
86
+ client_id: CLIENT_ID,
87
+ grant_type: 'authorization_code',
88
+ code: code,
89
+ redirect_uri: REDIRECT_URI
90
+ });
91
+ document.getElementById('status').textContent = 'Exchanging code for token...';
92
+ const resp = await fetch(HF_TOKEN_URL, {
93
+ method: 'POST',
94
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
95
+ body
96
+ });
97
+ const data = await resp.json();
98
+ if (data.access_token) {
99
+ localStorage.setItem('hf_oauth_token', data.access_token);
100
+ // Fetch userinfo
101
+ const respUser = await fetch('https://huggingface.co/oauth/userinfo', {
102
+ headers: { Authorization: `Bearer ${data.access_token}` }
103
+ });
104
+ const userinfo = await respUser.json();
105
+ const userinfoStr = JSON.stringify(userinfo, null, 2);
106
  localStorage.setItem('hf_oauth_userinfo', userinfoStr);
107
  showLoggedIn(userinfoStr);
108
  // Clean up URL
109
  window.history.replaceState({}, '', window.location.pathname);
110
  } else {
111
+ document.getElementById('status').textContent = 'OAuth failed: ' + JSON.stringify(data);
112
+ showLoggedOut();
 
 
 
 
 
 
113
  }
114
+ return;
115
+ }
116
+
117
+ // Already logged in?
118
+ const token = localStorage.getItem('hf_oauth_token');
119
+ const userinfo = localStorage.getItem('hf_oauth_userinfo');
120
+ if (token && userinfo) {
121
+ showLoggedIn(userinfo);
122
+ } else {
123
  showLoggedOut();
124
  }
125
  };
test.md ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adding a Sign-In with HF button to your Space
2
+
3
+ You can enable a built-in sign-in flow in your Space by seamlessly creating and associating an [OAuth/OpenID connect](https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc) app so users can log in with their HF account.
4
+
5
+ This enables new use cases for your Space. For instance, when combined with [Persistent Storage](https://huggingface.co/docs/hub/spaces-storage), a generative AI Space could allow users to log in to access their previous generations, only accessible to them.
6
+
7
+ <Tip>
8
+ If your Space uses **Gradio**, we recommend following the [Gradio OAuth integration guide](https://www.gradio.app/guides/sharing-your-app#o-auth-login-via-hugging-face), which is simpler and built-in.
9
+ **Only follow this guide if you are _not_ using Gradio** (e.g. static, Streamlit, custom JS/Node/Python apps).
10
+ </Tip>
11
+
12
+ <Tip>
13
+ You can also use the HF OAuth flow to create a "Sign in with HF" flow in any website or App, outside of Spaces. [Read our general OAuth page](./oauth).
14
+ </Tip>
15
+
16
+ **This integration can be done in 2 steps:**
17
+ 1. Enable OAuth in your Space's configuration
18
+ 2. Add the “Sign in with HF” button to your UI and handle authentication in your code
19
+
20
+ ## 1. Enable OAuth in your Space's configuration
21
+
22
+ All you need to do is add `hf_oauth: true` to your Space's metadata inside your `README.md` file.
23
+
24
+ Here's an example of metadata for a Gradio Space:
25
+
26
+ ```yaml
27
+ title: Gradio Oauth Test
28
+ emoji: 🏆
29
+ colorFrom: pink
30
+ colorTo: pink
31
+ sdk: gradio
32
+ sdk_version: 3.40.0
33
+ python_version: 3.10.6
34
+ app_file: app.py
35
+
36
+ hf_oauth: true
37
+ # optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
38
+ hf_oauth_expiration_minutes: 480
39
+ # optional, see "Scopes" below. "openid profile" is always included.
40
+ hf_oauth_scopes:
41
+ - read-repos
42
+ - write-repos
43
+ - manage-repos
44
+ - inference-api
45
+ # optional, restrict access to members of specific organizations
46
+ hf_oauth_authorized_org: ORG_NAME
47
+ hf_oauth_authorized_org:
48
+ - ORG_NAME1
49
+ - ORG_NAME2
50
+ ```
51
+
52
+ You can check out the [configuration reference docs](./spaces-config-reference) for more information.
53
+
54
+ This will add the following [environment variables](https://huggingface.co/docs/hub/spaces-overview#helper-environment-variables) to your space:
55
+
56
+ - `OAUTH_CLIENT_ID`: the client ID of your OAuth app (public)
57
+ - `OAUTH_CLIENT_SECRET`: the client secret of your OAuth app
58
+ - `OAUTH_SCOPES`: scopes accessible by your OAuth app.
59
+ - `OPENID_PROVIDER_URL`: The URL of the OpenID provider. The OpenID metadata will be available at [`{OPENID_PROVIDER_URL}/.well-known/openid-configuration`](https://huggingface.co/.well-known/openid-configuration).
60
+
61
+ As for any other environment variable, you can use them in your code by using `os.getenv("OAUTH_CLIENT_ID")`, for example.
62
+
63
+ <Tip warning={true}>
64
+ For Spaces OAuth integration, you do NOT need to configure anything in your Hugging Face account settings.
65
+ Everything is managed by adding metadata to your Space and handling the authentication flow in your code.
66
+ </Tip>
67
+
68
+ ### Scopes
69
+
70
+ The following scopes are always included for Spaces:
71
+
72
+ - `openid`: Get the ID token in addition to the access token.
73
+ - `profile`: Get the user's profile information (username, avatar, etc.)
74
+
75
+ Those scopes are optional and can be added by setting `hf_oauth_scopes` in your Space's metadata:
76
+
77
+ - `email`: Get the user's email address.
78
+ - `read-billing`: Know whether the user has a payment method set up.
79
+ - `read-repos`: Get read access to the user's personal repos.
80
+ - `write-repos`: Get write/read access to the user's personal repos.
81
+ - `manage-repos`: Get full access to the user's personal repos. Also grants repo creation and deletion.
82
+ - `inference-api`: Get access to the [Inference API](https://huggingface.co/docs/inference-providers/index), you will be able to make inference requests on behalf of the user.
83
+ - `write-discussions`: Open discussions and Pull Requests on behalf of the user as well as interact with discussions (including reactions, posting/editing comments, closing discussions, ...). To open Pull Requests on private repos, you need to request the `read-repos` scope as well.
84
+
85
+ ### Accessing organization resources
86
+
87
+ By default, the oauth app does not need to access organization resources.
88
+
89
+ But some scopes like `read-repos` or `read-billing` apply to organizations as well.
90
+
91
+ The user can select which organizations to grant access to when authorizing the app. If you require access to a specific organization, you can add `orgIds=ORG_ID` as a query parameter to the OAuth authorization URL. You have to replace `ORG_ID` with the organization ID, which is available in the `organizations.sub` field of the userinfo response.
92
+
93
+ ## 2. Add the sign-in button and handle authentication in your code
94
+
95
+ You now have all the information to add a "Sign-in with HF" button to your Space and implement the authentication flow. Some libraries ([Python](https://github.com/lepture/authlib), [NodeJS](https://github.com/panva/node-openid-client)) can help you implement the OpenID/OAuth protocol.
96
+
97
+ Gradio and huggingface.js also provide **built-in support**, making implementing the Sign-in with HF button a breeze; you can check out the associated guides with [gradio](https://www.gradio.app/guides/sharing-your-app#o-auth-login-via-hugging-face) and with [huggingface.js](https://huggingface.co/docs/huggingface.js/hub/README#oauth-login).
98
+
99
+ Basically, you need to:
100
+
101
+ - **Choose a redirect URL** that targets your Space (e.g., `https://{SPACE_HOST}/login/callback`).
102
+
103
+ - **Redirect the user** to `https://huggingface.co/oauth/authorize?redirect_uri={REDIRECT_URI}&scope=openid%20profile&client_id={CLIENT_ID}&state={STATE}`, where `STATE` is a random string that you will need to verify later.
104
+
105
+ - **Handle the callback** on `/auth/callback` or `/login/callback` (or your own custom callback URL) and verify the `state` parameter.
106
+
107
+ - Use the `code` query parameter to **get an access token and id token** from `https://huggingface.co/oauth/token` (POST request with `client_id`, `code`, `grant_type=authorization_code` and `redirect_uri` as form data, and with `Authorization: Basic {base64(client_id:client_secret)}` as a header).
108
+
109
+ <Tip warning={true}>
110
+
111
+ You should use `target=_blank` on the button to open the sign-in page in a new tab, unless you run the space outside its `iframe`. Otherwise, you might encounter issues with cookies on some browsers.
112
+
113
+ </Tip>
114
+
115
+ ## Examples
116
+
117
+ ### Code (with [huggingface.js](https://huggingface.co/docs/huggingface.js/hub/README#oauth-login))
118
+
119
+ ```js
120
+ import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
121
+
122
+ const oauthResult = await oauthHandleRedirectIfPresent();
123
+
124
+ if (!oauthResult) {
125
+ // If the user is not logged in, redirect to the login page
126
+ window.location.href = await oauthLoginUrl();
127
+ }
128
+
129
+ // You can use oauthResult.accessToken, oauthResult.userInfo among other things
130
+ console.log(oauthResult);
131
+ ```
132
+
133
+ ### Spaces
134
+
135
+ - [Client-Side in a Static Space (huggingface.js)](https://huggingface.co/spaces/huggingfacejs/client-side-oauth) – very simple JavaScript example.
136
+ - [Hugging Chat (NodeJS/SvelteKit)](https://huggingface.co/spaces/huggingchat/chat-ui)
137
+ - [Inference Widgets (Auth.js/SvelteKit)](https://huggingface.co/spaces/huggingfacejs/inference-widgets) – uses the `inference-api` scope to make inference requests on behalf of the user.
138
+ - [Gradio test app](https://huggingface.co/spaces/Wauplin/gradio-oauth-test)