authlib / demo-oauth-client Goto Github PK
View Code? Open in Web Editor NEWDemo for Flask, Django, and Starlette OAuth clients with Authlib>=v0.13
Home Page: https://docs.authlib.org/en/latest/client/
Demo for Flask, Django, and Starlette OAuth clients with Authlib>=v0.13
Home Page: https://docs.authlib.org/en/latest/client/
Do the tokens in these demos expire? For example in flask-google-login/app.py I see that the user info is stored in the session: session['user'] = token['userinfo']
, but I don't see any logic that has the webpage logout the user once the token expires. Through what means does authlib verify token validity?
Hi, I'm trying to make a flask oauth client that will be able to link a user account to an account in the oauth server (using the authlib flask oauth server example). I have not been able to find any examples or documentation on how to revoke a token. In my case it would be usefull to revoke a token if the user decides to unlikn the accounts.
I have tried resp = oauth.test.post(url, token=token)
where the url points to the /oauth/revoke
from the server example. The response from this is a 401 invalid client.
Is there a better way to do this? What am I missing?
Thanks
When there are more than 1 oauth client to connect, the application always picks the last one in the definition instead of any of the defined ones. There should be a way to keep every client registered and usable in subsequent calls. This is the example:
# 1. Register client_1
oauth.register(
"client_1",
client_id = client_id,
client_secret = client_secret,
client_kwargs = {
"scope": "openid profile email",
},
server_metadata_url = f'{domain}/.well-known/openid-configuration'
)
# 2. Register client_2
oauth.register(
"client_2",
client_id = client_id_2,
client_secret = client_secret_2,
client_kwargs = {
"scope": "openid profile email",
},
server_metadata_url = f'{domain_2}/.well-known/openid-configuration'
)
@app.route('client_1/login')
def client_1_login():
return oauth.client_1.....
@app.route('client_2/login')
def client_2_login():
return oauth.client_2....
when someone accesses route client_1/login, an error will raise with the following traceback:
Traceback (most recent call last):
File "/usr/src/app/.local/lib/python3.9/site-packages/flask/app.py", line 2077, in wsgi_app
response = self.full_dispatch_request()
File "/usr/src/app/.local/lib/python3.9/site-packages/flask/app.py", line 1525, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/src/app/.local/lib/python3.9/site-packages/flask_cors/extension.py", line 165, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/usr/src/app/.local/lib/python3.9/site-packages/flask/app.py", line 1523, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/src/app/.local/lib/python3.9/site-packages/flask/app.py", line 1509, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/usr/src/app/app/views/auth.py", line xxx, in client_1_login
token = oauth.client_1.authorize_access_token()
File "/usr/src/app/.local/lib/python3.9/site-packages/authlib/integrations/base_client/registry.py", line 118, in __getattr__
raise AttributeError('No such client: %s' % key)
AttributeError: No such client: client_1
Running the master branch code (and alpha v1 releases) I'm running into an issue where this line causes the following error:
Traceback (most recent call last):
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 369, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 59, in __call__
return await self.app(scope, receive, send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/fastapi/applications.py", line 208, in __call__
await super().__call__(scope, receive, send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/applications.py", line 112, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/middleware/sessions.py", line 75, in __call__
await self.app(scope, receive, send_wrapper)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/routing.py", line 580, in __call__
await route.handle(scope, receive, send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/routing.py", line 241, in handle
await self.app(scope, receive, send)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/routing.py", line 52, in app
response = await func(request)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/fastapi/routing.py", line 219, in app
raw_response = await run_endpoint_function(
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/fastapi/routing.py", line 152, in run_endpoint_function
return await dependant.call(**values)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/app.py", line 47, in auth
user = await oauth.google.parse_id_token(request, token)
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/authlib/integrations/base_client/async_openid.py", line 59, in parse_id_token
token['id_token'],
File "/Users/sondrelg/Documents/demo-oauth-client/fastapi-google-login/venv/lib/python3.9/site-packages/starlette/requests.py", line 68, in __getitem__
return self.scope[key]
KeyError: 'id_token'
The problem seems to stem from bad example code:
@app.get('/auth')
async def auth(request: Request):
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = await oauth.google.parse_id_token(request, token)
The last line here does not match the function signature:
async def parse_id_token(self, token, nonce, claims_options=None):
So when we hit line 59, we try to index on the request object.
Changing my code to this works:
user = await oauth.google.parse_id_token(token, None)
What should I pass for nonce
? None
seems like the wrong thing to pass here ๐
Environment:
I've made the example work by changing at
https://github.com/authlib/demo-oauth-client/blob/master/django-google-login/project/views.py#L31
user = oauth.google.parse_id_token(request, token)
to
user = oauth.google.parse_id_token(token, None)
I am trying to implement Oauth with linkedin but getting scope error. How shall I pass the scope to oauth in case of linkedin?
Adding code below for reference:
linkedin = oauth.register(
name='linkedin',
client_id='',
client_secret='',
base_url='https://www.linkedin.com/oauth/v2/',
access_token_url='https://www.linkedin.com/oauth/v2/accessToken',
authorize_url='https://www.linkedin.com/oauth/v2/authorization',
access_token_params=None,
authorize_params=None,
client_kwargs={'scope': 'r_liteprofile%20r_emailaddress%20w_member_social'}
)
@app.route('/')
def index():
email = dict(session).get('email', None)
return f"Hello, {email}"
@app.route('/login')
def login():
redirect_uri = url_for('authorize', _external=True)
return oauth.linkedin.authorize_redirect(redirect_uri)
@app.route('/authorize')
def authorize():
token = oauth.linkedin.authorize_access_token()
resp = oauth.linkedin.get('userinfo')
resp.raise_for_status()
user_info = resp.json()
print(user_info)
session['token'] = token
session['user'] = user_info
session['email'] = user_info['emailaddress']
# do something with the token and profile
return redirect('/')
if __name__ == '__main__':
app.run(debug=True, port=8080)
The flask-google-login example requires the requests module.
Otherwise:
env\lib\site-packages\authlib\integrations_client\oauth_registry.py", line 49, in use_oauth_clients
clients = self.AVAILABLE_CLIENTS[name]
KeyError: 'requests'
to resolve this:
pip install requests
I try to use this demo on Azure, but some things wrong when I access oauth.azure.authorize_access_token()
.
It shows that the request dosen't contain the client_secret
Does anyone meet the same issue? Or some demos recommended
Is there any chance to see a demo for Github OAuth?
https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps
Please add or note in the documentation examples
how passing request body as json than data parameter ( .post(json=) instead of .post(data=) )
for a HTTP post call via OAuth2Session ( Session ' s request ) 's required for Google APIs .
Reference -
Calendar API example at
https://stackoverflow.com/questions/29714710/google-calendar-api-freebusy-jquery-post-gets-400-bad-request-error
Please update the documentation on refresh token at
https://docs.authlib.org/en/latest/client/frameworks.html
or
https://docs.authlib.org/en/latest/client/flask.html
as per the suggested solution at
https://stackoverflow.com/questions/62293888/obtaining-and-storing-refresh-token-using-authlib-with-flask
Parameter to specify add in the remote application as per
" .. The first step is register a remote application on the OAuth registry via oauth.register method "
at https://docs.authlib.org/en/latest/client/flask.html
to obtain retrieve get the refresh token
is
authorize_params={'access_type': 'offline'}
google = oauth.register(
'google',
# ...
authorize_params={'access_type': 'offline'},
)
as per
https://stackoverflow.com/a/62296675
and
'prompt': 'consent' in the client_kwargs parameter in the remote application on the OAuth registry via oauth.register method
if required
client_kwargs = {'prompt':'consent'}
as per
https://stackoverflow.com/questions/62293888/obtaining-and-storing-refresh-token-using-authlib-with-flask#comment110182471_62296675
Thank you
Using authlib 1.0.0rc1 and running the demo code.
I am running into an the issue where the id_token is in the response, but is not being parsed. I suspect this and issue #17 might be related.
The userinfo is not being added to the token, so the following returns None
.
Calling oauth.google.parse_id_token(token, None)
to manually get userinfo
is my current workaround.
I suspect that the state query param is not getting picked up here:
https://github.com/lepture/authlib/blob/169c7dcfc47478c8d55553cc95fb0f5578162b77/authlib/integrations/flask_client/apps.py#L100
Hi,
I'm not getting much help from google or stackoverflow so I hope you can help.
I have Google OAuth working with FastAPI and authlib, following your example and building off the official security tutorial. This is working via FastAPI's built-in swagger UI, and I can see that I'm only able to access my protected routes if I have logged in with my google user via the fastapi.security.OAuth2PasswordBearer
form, which is good.
However, I can't replicate this via my "frontend", which is just some html served by fastapi with a button that triggers the typical Google OAuth form. That is, the button contains <a href="/login">Google</a>
. This ultimately takes me to my /token
endpoint and prints the {"access_token": <my_token>, "token_type": "bearer"}
, which seems good.
In reality, once this token is correctly received, I need to redirect to one of my protected routes. In order to redirect, I can't simply use that <a>
. Instead, I need to bind an XMLHttpRequest()
to onclick
that simulate's the href="/login"
with a GET
and, if that goes well, then redirects to my protected route. But unfortunately that XMLHttpRequest()
results in https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=<client_id>&redirect_uri=http%3A%2F%2F127.0.0.1%3A8004%2Fapi%2Fauth%2Fgoogle&scope=openid+email+profile&state=<state>' (redirected from 'http://127.0.0.1:8004/api/auth/login/google') from origin 'http://127.0.0.1:8004' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
. I have tried a variety of different headers and referrerPolicy, as well as CORSMiddleware
but so far I am unable to simulate the <a>
with my XMLHttpRequest()
.
To be honest, I'm not sure I'm approaching this the right way. Ultimately I'm just trying to get your example code to work not just via swagger but also via my html.
I am trying this demo code but I am getting the error bad request 400 KeyError: code.any idea?
Hi thanks for the demos, I tried some demos, the demo for flask works flawlessly, but when I tried the demos for fastapi & starlette (all google demos), it throws the error:
File "./app.py", line 40, in login
return await oauth.google.authorize_redirect(request, redirect_uri)
File "/usr/local/lib/python3.7/site-packages/authlib/integrations/starlette_client/integration.py", line 52, in authorize_redirect
rv = await self.create_authorization_url(redirect_uri, **kwargs)
File "/usr/local/lib/python3.7/site-packages/authlib/integrations/base_client/async_app.py", line 51, in create_authorization_url
metadata = await self.load_server_metadata()
File "/usr/local/lib/python3.7/site-packages/authlib/integrations/base_client/async_app.py", line 20, in load_server_metadata
metadata = await self._fetch_server_metadata(self._server_metadata_url)
File "/usr/local/lib/python3.7/site-packages/authlib/integrations/base_client/async_app.py", line 204, in _fetch_server_metadata
resp = await client.request('GET', url, withhold_token=True)
File "/usr/local/lib/python3.7/site-packages/authlib/integrations/httpx_client/oauth2_client.py", line 95, in request
method, url, auth=auth, **kwargs)
File "/usr/local/lib/python3.7/site-packages/httpx/_client.py", line 1482, in request
request, auth=auth, allow_redirects=allow_redirects, timeout=timeout
File "/usr/local/lib/python3.7/site-packages/httpx/_client.py", line 1566, in send
auth = self._build_request_auth(request, auth)
File "/usr/local/lib/python3.7/site-packages/httpx/_client.py", line 433, in _build_request_auth
self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)
File "/usr/local/lib/python3.7/site-packages/httpx/_client.py", line 425, in _build_auth
raise TypeError(f'Invalid "auth" argument: {auth!r}')
TypeError: Invalid "auth" argument: <httpx._config.UnsetType object at 0x10a790950>
In authlib/integrations/base_client/async_app.py
line 204
async def _fetch_server_metadata(self, url):
async with self._get_oauth_client() as client:
resp = await client.request('GET', url, withhold_token=True)
return resp.json()
withhold_token
is set to True, but didn't set auth
, which cases auth
to be UNSET
in authlib/integrations/httpx_client/oauth2_client.py
line 95
async def request(self, method, url, withhold_token=False, auth=UNSET, **kwargs):
if not withhold_token and auth is UNSET:
if not self.token:
raise MissingTokenError()
if self.token.is_expired():
await self.ensure_active_token()
auth = self.token_auth
return await super(AsyncOAuth2Client, self).request(
method, url, auth=auth, **kwargs)
and throw the above error.
My authlib version is:
google-auth-oauthlib==0.4.1
oauthlib==2.1.0
requests-oauthlib==1.0.0
httpx==0.18.2
Any fix for this ?
Hi I am using OAuth with starlette.
I am registering a service, which looks like this
{ "client_id": "xxx", "client_secret": "xxx", "authorization_endpoint": "https://orcid.org/oauth/authorize", "token_endpoint": "https://orcid.org/oauth/token", "userinfo_endpoint": "https://orcid.org/oauth/userinfo", "server_metadata_url": "https://orcid.org/.well-known/openid-configuration", }
which looks like this:
{ "token_endpoint_auth_signing_alg_values_supported" : [ "RS256" ], "id_token_signing_alg_values_supported" : [ "RS256" ], "userinfo_endpoint" : "https://orcid.org/oauth/userinfo", "authorization_endpoint" : "https://orcid.org/oauth/authorize", "token_endpoint" : "https://orcid.org/oauth/token", "jwks_uri" : "https://orcid.org/oauth/jwks", "claims_supported" : [ "family_name", "given_name", "name", "auth_time", "iss", "sub" ], "scopes_supported" : [ "openid" ], "subject_types_supported" : [ "public" ], "response_types_supported" : [ "code", "id_token", "id_token token" ], "claims_parameter_supported" : false, "token_endpoint_auth_methods_supported" : [ "client_secret_post" ], "grant_types_supported" : [ "authorization_code", "implicit", "refresh_token" ], "issuer" : "https://orcid.org" }
when I later call
authorize_redirect
I am getting this error message:
`
res: RedirectResponse = await service.authorize_redirect(request, self.helper.redirect_uri)
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/starlette_client/integration.py", line 52, in authorize_redirect
rv = await self.create_authorization_url(redirect_uri, **kwargs)
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/base_client/async_app.py", line 51, in create_authorization_url
metadata = await self.load_server_metadata()
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/base_client/async_app.py", line 20, in load_server_metadata
metadata = await self._fetch_server_metadata(self._server_metadata_url)
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/base_client/async_app.py", line 204, in _fetch_server_metadata
resp = await client.request('GET', url, withhold_token=True)
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/httpx_client/oauth2_client.py", line 94, in request
return await super(AsyncOAuth2Client, self).request(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1481, in request
response = await self.send(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1566, in send
auth = self._build_request_auth(request, auth)
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 433, in _build_request_auth
self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 425, in _build_auth
raise TypeError(f'Invalid "auth" argument: {auth!r}')
TypeError: Invalid "auth" argument: <httpx._config.UnsetType object at 0x7fc823087370>
`
HOWEVER,
when I register the service without:
server_metadata_url
It works.
Django refresh token update_token doesn't refresh the token; example doesn't have auto refresh token implemented. Any suggestion to get it working.
I'm trying out the examples that are here for google login with starlette and fastapi. When I follow the normal flow everything works as expected. But if for example I try to access directly the /auth
url the application crashes with the following stacktrace:
INFO: 127.0.0.1:41144 - "GET /auth HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/fastapi/applications.py", line 190, in __call__
await super().__call__(scope, receive, send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/middleware/sessions.py", line 75, in __call__
await self.app(scope, receive, send_wrapper)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
response = await func(request)
File "./app.py", line 46, in auth
token = await oauth.google.authorize_access_token(request)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/authlib/integrations/starlette_client/integration.py", line 62, in authorize_access_token
params = self.retrieve_access_token_params(request)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/authlib/integrations/base_client/base_app.py", line 145, in retrieve_access_token_params
params = self._retrieve_oauth2_access_token_params(request, params)
File "/home/jpr/.virtualenvs/tmp-61b723d80f5058/lib/python3.7/site-packages/authlib/integrations/base_client/base_app.py", line 126, in _retrieve_oauth2_access_token_params
raise MismatchingStateError()
authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF Warning! State not equal in request and response.
which in a way is expected since the authorize_redirect
part was skipped. So my question is two-fold:
/auth
? Since the user is currently not logged in, is there a way to enforce the correct flow?I'm after a demo for facebook. Can't reach a way to make the oauth.register for facebook. I've tried to adapt the Twitter one for facebook and including the App ID and App Secret, but when redirect to the facebook login page, I get a "Invalid App ID".
Is there a way to refresh token using starlette integration?
import json
from fastapi import FastAPI
from starlette.config import Config
from starlette.requests import Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, RedirectResponse
from authlib.integrations.starlette_client import OAuth, OAuthError
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="!secret")
config = Config('.env')
oauth = OAuth(config)
oauth.register(
name='github',
access_token_url='https://github.com/login/oauth/access_token',
authorize_url='https://github.com/login/oauth/authorize',
api_base_url='https://api.github.com/',
userinfo_endpoint='https://api.github.com/user',
client_kwargs={
'scope': 'user:email'
}
)
@app.route('/')
async def homepage(request: Request):
user = request.session.get('user')
if user:
data = json.dumps(user)
html = (
f'<pre>{data}</pre>'
'<a href="/logout">logout</a>'
)
return HTMLResponse(html)
return HTMLResponse('<a href="/login">login</a>')
@app.route('/login')
async def login(request: Request):
redirect_uri = request.url_for('token')
return await oauth.github.authorize_redirect(request, redirect_uri)
@app.route('/token')
async def token(request: Request):
try:
token = await oauth.github.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = await oauth.github.userinfo(token=token)
request.session['user'] = user.get('name')
return RedirectResponse(url='/')
@app.route('/logout')
async def logout(request: Request):
request.session.pop('user', None)
return RedirectResponse(url='/')
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='127.0.0.1', port=8000)
Hello,
I am using the starlette Google login demo, slightly modified for my use case, and I notice that when I use the logout function it doesn't properly clear the user from the browser cache and instead will log me back into the previously signed into account. I have no idea how to fix this presently. Any guidance would be appreciated. Thank you in advance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.