badgateway / oauth2-client Goto Github PK
View Code? Open in Web Editor NEWOAuth2 client for Node and browsers
Home Page: https://www.npmjs.com/package/@badgateway/oauth2-client
License: MIT License
OAuth2 client for Node and browsers
Home Page: https://www.npmjs.com/package/@badgateway/oauth2-client
License: MIT License
hello,
I am trying to use you libraries in a frontend, I am not an expert in nodejs, but it appears that we cannot use an npm module as is. While looking at your build scripts, a browser version can be generate.
While looking on internet it appears that there is already a library with same name: https://www.jsdelivr.com/package/npm/oauth2-client-js
Can you release something? or the only way is to generate the browser version and to embed this js in my front?
Thank you,
CΓ©dric,
Refresh token grant takes an optional parameter scope
according to the oauth2 spec
The client just takes the (refresh) token as a parameter link:
async refreshToken(token: OAuth2Token): Promise<OAuth2Token> {
How about adding an optional scope to the client.refreshToken method.
Can be bypassed for now by using the request('tokenEndpoint', body)
method directly.
Also:
README.md seems to have the method name incorrectly as refresh instead of refreshToken.
Thanks!
I want to use OAuth public client with code flow and PKCE. The issue is with token request, which must include client_id in this case according to the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3. This library adds the client_id to the token request only if the "settins.authenticationMethod" equals to "client_secret_post" which is wrong IMO. It should be added even if no authenticationMethod is used.
Another issue is if I do not provide "authenticationMethod" in settings, this library will use the first authentication method from OAuth server metadata (token_endpoint_auth_methods_supported). For example, if the server metadata contains ["client_secret_basic"], then the library will include Authorization HTTP header with basic authentication even if I do not intend to use any.
(and add missing ones)
π
Looks like the dist js files in 2.1.0 don't contain the latest changes
Odd, because the .d.ts does have correct types.
Side note: have you considered publishing via GitHub actions so the build+publish is handled consistently rather than manually publishing? I'm happy to offer a PR for that if you'd like.
Thanks
I'm using authorization_code
grant, and got it working like so:
const client = new OAuth2Client({
"authorizationEndpoint": "/auth",
"tokenEndpoint": new URL("./token", AUTH_URL).href,
"server": "https://developer.foo.com/oauth",
"clientId": INTEGRATION_KEY,
"clientSecret": SECRET_KEY
});
From the README:
// OAuth2 client secret. Only required for 'client_credentials', 'password'
// flows. You should not specify this for authorization_code.
I'm not sure why this suggests not using the clientSecret
. I needed it for it to work.
From the types:
/**
* The hostname of the OAuth2 server.
* If provided, we'll attempt to discover all the other related endpoints.
*
* If this is not desired, just specify the other endpoints manually.
*
* This url will also be used as the base URL for all other urls. This lets
* you specify all the other urls as relative.
*/
server?: string;
I couldn't get relative URLs to work, and looking here, I think these need to be ./
instead of /
.
This would also explain why I needed to specify tokenEndpoint
at all, since the default should have worked.
From the README:
/**
* You are responsible for implementing this function.
* it's purpose is to supply the 'initial' oauth2 token.
*/
getNewToken: async () => {
// Example
return client.clientCredentials();
// Another example
- return client.authorizationCode({
code: '..',
redirectUri: '..',
});
This needed to be client.authorizationCode.getToken()
.
Hello,
I'm using this library with PKCE and FetchWrapper
and I am having trouble with the refresh tokens. I instantiate FetchWrapper
like so:
const fetchWrapper = new OAuth2Fetch({
client,
getNewToken: () => {
// Set the status to expired
useAuthStore.setState({ status: AuthStatus.EXPIRED });
return null; // Fail this step, we don't want to log out until the user does so explicitly
},
storeToken: (token) => {
useAuthStore.setState({
token,
});
},
getStoredToken: () => useAuthStore.getState().token,
});
From my understanding, this fetchWrapper should automatically refresh the tokens when they expire and it tries to send a request.
Indeed, it does appear that it is sending a refresh request to the token endpoint. However, the server rejects it, because it does not include the code_verifier
. Before using this library, I built a custom solution which does send the code_verifier in the refresh requests, and I can verify that if it sends that then it does work.
I believe the solution is to make some API change that allows the FetchWrapper
to access the code_verifier
(potentially by storing the code_verifier
in the OAuth2Token
).
Thank you.
Google requires ?access_type=offline
in the authorization url:
https://accounts.google.com/o/oauth2/auth?access_type=offline&prompt=consent
This library concats ?client_id=
which leads to two ?
.
A more elegant way would be to use new URL()
and then .searchParams.append('client_id', ...)
.
Because of a misplaced !
in client.ts, discovery document isn't accepted if its content-type is right:
``` if (!resp.headers.has('Content-Type') || resp.headers.get('Content-Type')!.startsWith('application/json')) {``
Additionnaly, when that is fixed, this code gives me wrong endpoints:
const authorizeParams = {
redirectUri: serviceWautherConfig.redirectUri,
codeVerifier: codeVerifier
};
const silentRefreshAuthorizeParams = {
redirectUri: serviceWautherConfig.silentRefreshRedirectUri,
codeVerifier: codeVerifier
};
const silentRefreshAuthorizeUriPromise = oidcClient.authorizationCode.getAuthorizeUri(silentRefreshAuthorizeParams);
const authorizeUriPromise = oidcClient.authorizationCode.getAuthorizeUri(authorizeParams);
const [authorizeUri, silentRefreshAuthorizeUri] = await Promise.all([authorizeUriPromise, silentRefreshAuthorizeUriPromise]);
returns this:
authorizeUrl = http://localhost:26800/realms/burger/protocol/openid-connect/auth?response_type=code&client_id=.....
silentRefreshAuthorizeUrl = http://localhost:26800/authorize?response_type=code&client_id=...
(second url seems to be defaulted instead of using the discovery document)
So people can store their tokens in browsers
What's the best way to attach scope to an OAuth client? I don't see it included in the types, but looking at the util.ts
code it's included.
I'm trying to figure out how to handle the situation where any number of requests may be in-flight at a moment when token refresh occurs as the result of a 401
.
In particular I'm debugging a situation in which I have, say, 8 parallel requests and only one is refreshing, and strangely (not sure if it's a result of the middleware or something else) the others continue without use of the middleware altogether.
I think the answer would be some sort of global state/singleton which coordinates an AbortController and signals all in-flight requests, and then retries them after the refresh?
I checked the library, it is very useful and readme is understandable to anyone. Applause for that.
I tried to run your source code, but unfortunately i got the error with client_credentials flow,
But then I checked your source code, there is no declaration for fetch anywhere in the files.
~\node_modules\@badgateway\oauth2-client\dist\client.js:157
const resp = await fetch(uri, {
^
ReferenceError: fetch is not defined;
A simple test file to reproduce the bug:
const { OAuth2Client} = require('@badgateway/oauth2-client');
const client = new OAuth2Client({
scope: [
'email',
'offline_access',
'openid'
],
grantType: 'client_credentials',
clientId: 'a*****************************',
clientSecret: '******************************3',
discoveryEndpoint: 'https://login.microsoftonline.com/*****************8/v2.0/.well-known/openid-configuration',
tokenEndpoint: 'https://login.microsoftonline.com/*****************8/oauth2/v2.0/token',
authorizationEndpoint: 'https://login.microsoftonline.com/*****************8/oauth2/v2.0/authorize'
});
const express = require('express');
async function main() {
const token = await client.clientCredentials();
console.log('called ::: ', token);
}
const app = express();
app.listen(3001, () => {
console.log(
Msal Node Auth Code Sample app listening on port ${3001}!
);
main();
});
Please help and provide a solution to fix it.
or equivalent
I'm using this and the Authorization header doesn't seem to be set in the request:
clientSecret
is set.
const oauth2Token = await client.authorizationCode.getToken({
code: code,
redirectUri: "http://localhost:3000/callback"
});
I'm wondering if this condition should be body.grant_type === 'authorization_code'
:
Lines 268 to 271 in 00f1cd3
When I updated from 2.0.18
to 2.2.1
I noticed that some of my tests stopped passing. Some of these tests relied on msw
. When used in a Node.js test environment, it alters the fetch
function initially supplied by jsdom
. However in update 2.2.0
, the current value of fetch is captured here. This means that if the constructor is called before msw
has finished mocking fetch, the initial jsdom
implementation is captured and used instead.
Previously, I had a file which looked like this:
import { OAuth2Client } from "@badgateway/oauth2-client";
import config from "./config";
export const client = new OAuth2Client({
server: config.server,
clientId: config.client_id,
tokenEndpoint: "/api/token",
authorizationEndpoint: config.authorization_endpoint,
});
This means that the constructor is called as soon as the module is imported, and msw
may not have had a chance to mock yet. The solution for me, was to defer the creation of the client until it is needed like so:
import { OAuth2Client } from "@badgateway/oauth2-client";
import config from "./config";
let _client: OAuth2Client | null = null;
export const getClient = () => {
if (!_client) {
_client = new OAuth2Client({
server: config.server,
clientId: config.client_id,
tokenEndpoint: "/api/token",
authorizationEndpoint: config.authorization_endpoint,
});
}
return _client;
};
However, this may not be obvious to new users of the library unfamiliar with the source code. A similar issue occurs with the getStoredToken
option, which is invoked immediately by the constructor, which can be an issue if the persistent store needs initialisation.
Expected behaviour for the first case would be that instead of capturing the value at the constructor, the client could check the existence of the custom implementation when it is needed, like this:
this.settings.fetch?.(...) ?? fetch(...)
This could be made into a method on the OAuth2Client
class:
customFetch(request: Request, init?: RequestInit | undefined) {
return this.settings.fetch?.(...) ?? fetch(...)
}
Alternatively, if this is intended behaviour it could be documented in the README, but this may be a breaking change.
For the second case the expected behaviour would be to only call getStoredToken
when actually making the request in a similar pattern.
If the authentication server client_credentials response has the expires_in formatted as a string grant_base.ts will throw an error.
Body Example:
{
access_token: "/DsAH2ykgeGGfbLY7GEhduWMCc9H0Gih1XVrUZQaeNJNJONEWJm0uuZpmrXSK5mzMXC5jEXSJVjYpF+Xy6FRdarw646T+4pPaJ5KMawlaVY=",
expires_in: "2700",
token_type: "Bearer",
}
Code block at line 114 in grant_base.ts:
if (
body.expires_in !== undefined && typeof body.expires_in !== "number"
) {
throw new TokenResponseError(
"expires_in is not a number",
response,
);
}
Testing for both number and string would handle this situation and in the tokens.expiresIn assignment below convert type from string to number if needed.
Access to fetch at from origin 'http://localhost:1420' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
is this supposed to happen?
Hi, iΒ΄m using version 7.1.1 of this library and i try to handle the case when refresh token is expired. There is the option onAuthError i found in your docs but canΒ΄t get it to work.
token refresh is working and the onTokenUpdate callback is triggered on update. Whereas on error with status code 401 the onAuthError callback is just not called.
halClient.use(oauth2({
clientId: 'managerclient',
clientSecret: 'secret',
accessToken: access_token,
refreshToken: refresh_token,
tokenEndpoint: endpoint,
onTokenUpdate: () => { // called on update
Logger.info('token refreshed');
},
onAuthError: (error) => { // not called.
Logger.error(error);
// redirect to login
dispatch(navigateToLoginRoute());
}
}));
Is there something iΒ΄m missing?
see:
HeroicKatora/oxide-auth#145
basically it seems that you should only use authorization: Basic
I am getting the following error in a browser: TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation at t.OAuth2Client.request
The problem appears to be that fetch binds to the settings object but it needs to be bound to the window. Probably the easiest fix is to pass the fetch call as a lambda in the client constructor like this:
const originalFetch = fetch;
const client = new OAuth2Client({
...
fetch: (...args) => originalFetch(...args),
});
I do not think this is a problem when running in a node environment. I am not sure how this plays out in a service worker.
The best fix is probably to change this:
Line 104 in fdd7f75
to something like what I've done in the constructor.
As a side note, I use a cached copy of the original fetch because I think it's best practice for all oauth related calls to protect against XSS. This provides a good explanation of why, though the next comment shows that there's still more a hacker could do. That's why it's even better to make sure this code can run in a properly implemented service worker.
Lib is great π, thanks for your hard work!
The Kinde management API requires an audience
be sent on client credentials grant
// client.ts L144
async clientCredentials(params?: { audience?: string, scope?: string[] }): Promise<OAuth2Token> {
const body:ClientCredentialsRequest = {
grant_type: 'client_credentials',
audience: params?.audience,
scope: params?.scope?.join(' '),
};
// ...
}
Hello,
I am trying to get OIDC to work using just the discovery endpoint. I did point it ti the .well-known/openid-configuration
url and checked that the correct endpoints are configured there. When I use this.client.getEndpoint('authorizationEndpoint')
it gets me the default configured /authorize
instead of what is configured in the .well-known. My best guess is that this happens because authorizationEndpoint
has a default value, so that one is returned before the code actually checking the discoveryEndpoint. This should not happen because if I give it an discorverEndpoint, I expect it to be used over default values.
Lines 283 to 295 in 7ce4f29
I thought this would work (notice the value of "server"):
const client = new OAuth2Client({
clientId: 'id',
clientSecret: 'secret',
server: 'https://api.schwabapi.com/v1/oauth',
});
export async function getAuthorizeUri() {
return client.authorizationCode.getAuthorizeUri({
redirectUri: schwabRedirectUri,
scope: ['readonly'],
});
}
But then getAuthorizeUri()
returns https://api.schwabapi.com/authorize?client_id=id&response_type=code&redirect_uri=________&scope=readonly
, where the URL is missing /v1/oauth
.
It should instead return https://api.schwabapi.com/v1/oauth/authorize?client_id=id&response_type=code&redirect_uri=________&scope=readonly
.
A workaround would be for me to prepend /v1/oauth
to the values for authorizationEndpoint
and tokenEndpoint
, but it would be convenient to just leave them appended to "server".
Am I misunderstanding something?
Thanks!
Hi there, looking at the client, I don't see a way to revoke a token. I believe (correct me if I'm wrong) that this is part of the OAuth spec, so it seems reasonable that the client ought to support it. For instance, the discovery server I'm working with returns a revocation_endpoint
.
If there's already a way to do this with the client, I apologize. Please point me in the right direction. Otherwise, I would be happy to take a stab at a PR.
There seems to be currently no way of getting endpoint URLs from the discovery document for some entries, e.g.: endSessionEndpoint etc.
As not all endpoints are specified and we can expect new ones to arise, maybe it would be a good option to be able to specify a free parameter for endpoints besides the existing enum.
Hello! First of all, thanks for your hard word on this project :)
While working with this library to replace an existing OIDC auth client, I noticed that there is currently only support for client_secret_basic
authentication method. This is a blocker for my team, because we are only allowed to enable client_secret_post
.
I went ahead and opened PR #84 which contains the changes need to support client_secret_post
for both the client_credentials
and password
grant types.
I've come across a response from a request('tokenEndpoint') which includes a property in the response body that OAuth2Client is ignoring. I've checked the RFC, and there's no "MUST NOT" or similar forbidding extra properties. And at least for this OAuth server, the ignored property would have been very useful to see earlier!
{
error: "invalid_request",
error_description: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
hint: "Authorization code has expired",
message: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
}
I'd like to request that the responseBody be included in the thrown OAuth2Error at the end of request()
, so that non-standard properties like hint can be found by developers.
It seems this project doesn't work with plain node is it?
I get errors like this:
./node_modules/fetch-mw-oauth2/src/fetch-wrapper.ts:66
const request = new Request(input, init);
^
ReferenceError: Request is not defined
The authorization code flow logic contains the lines below. These make it so that the state
query parameter is required in the response, regardless of whether it was passed in the authorize call. I have a fix for this but can't seem to push a branch.
if (!queryParams.has('code')) throw new Error(`The url did not contain a code parameter ${url}`);
if (!queryParams.has('state')) throw new Error(`The url did not contain state parameter ${url}`);
if (params.state && params.state !== queryParams.get('state')) {
throw new Error(`The "state" parameter in the url did not match the expected value of ${params.state}`);
}
I'm currently trying to get authorization code flows to work with 2.0.17. This version is supposed to include a fix for passing scope
correctly, but despite this my code is still not working. Upon digging into node_modules
, it looks like the src
folder contains the correct code, but the dist
folder's built code does not correspond to it (and is thus missing the fixes required to get scope
to work):
$ cat package.json | grep oauth2-client
"@badgateway/oauth2-client": "^2.0.17",
$ cat node_modules/@badgateway/oauth2-client/src/client/authorization-code.ts | grep "const query:" -A15
const query: AuthorizationQueryParams = {
client_id: this.client.settings.clientId,
response_type: 'code',
redirect_uri: params.redirectUri,
code_challenge_method: codeChallenge?.[0],
code_challenge: codeChallenge?.[1],
};
if (params.state) {
query.state = params.state;
}
if (params.scope) {
query.scope = params.scope.join(' ');
}
return authorizationEndpoint + '?' + generateQueryString(query);
$ cat node_modules/@badgateway/oauth2-client/dist/client/authorization-code.js | grep "const query =" -A15
const query = {
client_id: this.client.settings.clientId,
response_type: 'code',
redirect_uri: params.redirectUri,
code_challenge_method: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[0],
code_challenge: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[1],
};
if (params.state) {
query.state = params.state;
}
return authorizationEndpoint + '?' + (0, client_1.generateQueryString)(query);
}
async getTokenFromCodeRedirect(url, params) {
const { code } = await this.validateResponse(url, {
state: params.state
});
Can anyone else reproduce this? I don't think it's just my setup -- the "Code" tab on npmjs.com seems to confirm that dist
and src
are out of sync in the packaged version of 2.0.17 (https://www.npmjs.com/package/@badgateway/oauth2-client?activeTab=explore). Cloning the repository and running npm install && make build
produces correct artifacts, so perhaps something went wrong with the release?
Thanks for the great library and any time you are able to give to this!
Line 427 in 7ce4f29
Shouldn't if (responseBody.error) {
read if (responseBody?.error) {
because the responseBody
might not be retrieved?
I'm not a big js guy so please forgive me if I'm doing a fridge temperature IQ move here but...
I'm using the fetch wrapper to send POST requests from a chrome extension to an app. I can oauth between the two successfully, but when a refreshToken happens from the fetch wrapper, my backend returns a 400 bad request.
Looking into this, I noticed the difference between the two requests is that the refresh requests are missing the scope.
I saw this PR which adds an optional scope param to refreshToken (https://github.com/badgateway/oauth2-client/pull/135/files) but it doesn't look like it's been added to the fetch wrapper https://github.com/badgateway/oauth2-client/blob/main/src/fetch-wrapper.ts#L157
Am i missing something? Is there a way to ensure the scope from the client makes it through to refresh requests in the fetch wrapper?
This code from authorization-code.ts
failed:
const arr = new Uint8Array(32);
crypto.webcrypto.getRandomValues(arr);
return base64Url(arr);
So I had to create my own solution:
function generateCodeVerifier() {
const emptyCodeVerifierArray = new Uint8Array(32);
const codeVerifierArray = crypto.getRandomValues(emptyCodeVerifierArray);
return btoa(String.fromCharCode(...codeVerifierArray));
}
I would like the option to execute the refresh timer without a refresh token.
In fetch-wrapper.ts https://github.com/badgateway/oauth2-client/blob/main/src/fetch-wrapper.ts#L255
The conditional
if (!this.token?.expiresAt || !this.token.refreshToken)
Causes it to not set a refreshTimer when no refreshToken is supplied. I would like to have to option to still execute the refresh logic even if no refresh token is supplied.
Since refreshToken will be called anyway when https://github.com/badgateway/oauth2-client/blob/main/src/fetch-wrapper.ts#L148 getToken is called.
Possible fix:
Replace the conditional with:
if (!this.token?.expiresAt)
Or maybe add an additional option like was done with this.options.scheduleRefresh
e.g. this.options.forceScheduleRefresh
?
Thanks in advance!
Greetings,
Koen
I would like the ability to add custom headers to requests made by OAuth2Client.OAuth2Client
Example usage would be something like;
const client = new Client({
server: 'https://auth-server.example/',
clientId: '...',
clientSecret: '...',
additionalHeaders: '...',
});
Which I guess should be used in the client.ts's async request method
I am having trouble using the library to connect to the Linkedin API. I am using the authorization_code flow, and it fails upon handling the redirection.
I am calling client.authorizationCode.getToken({code, redirectUri, state})
and getting the error from the server:
OAuth2 error invalid_request. A required parameter \"client_secret\" is missing
The Linkedin documentation does mention that the client_secret (and client_id) are required: https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fconsumer%2Fcontext&tabs=HTTPS1#step-2-request-an-authorization-code
I just want to know whether the issue is with Linkedin not following the standard, or if this could be an extra option to add to the library (eg. add options like includeClientSecret
to GetTokenParams
?), in which case I would do a PR.
This following code fails because getStoredToken is always invoked as an async function, meaning that fetchWrapper.fetch()
will be called before getStoredToken()
has even been called. See this async IIFE. By the time the fetchWrapper.fetch()
is called, the private property fetchWrapper.token
is still null, so getToken()
attempts to refresh it, which fails, invoking refreshToken()
which attempts to refresh the non-existent token, which fails invoking getNewToken()
which also fails, finally invoking onError
, which prompts the user to log in again. When this is finished, control returns to the async IIFE, which finally sets the token to the correct value, and the next time fetchWrapper.fetch()
is invoked, it behaves correctly. I have verified this using the debugger in Chrome DevTools. It is possible to create an async constructor by returning an IIAFE from the constructor. However, this could be tricky without introducing a breaking change. Alternatively, the IIAFE could be stored in a private property which could be awaited inside of the fetchWrapper.fetch()
function, ensuring that getStoredToken()
has been invoked. Until a fix is released, I have a temporary workaround, but it is quite hacky, so I would appreciate a stable fix.
Problematic Code
const fetchWrapper = new OAuth2Fetch({
client,
getNewToken: () => {
return null; // Fail this step, we don't want to log out until the user does so explicitly
},
storeToken,
getStoredToken,
onError: (error) => {
// Prompt the user to log in again
},
});
const res = await fetchWrapper.fetch(...)
Temporary workaround
let ready = false;
const initialiseFetchWrapper = async () => {
const fetchWrapper = new OAuth2Fetch({
client,
getNewToken: () => {
return null; // Fail this step, we don't want to log out until the user does so explicitly
},
storeToken,
getStoredToken,
onError: (error) => {
if (ready) {/* Prompt user to log in */}
},
});
try {
await fetchWrapper.getToken();
} catch {
// Ignore we expect this to error the first time
}
ready = true;
return fetchWrapper;
};
let _fetchWrapper: OAuth2Fetch | null = null;
// We defer initialisation until the first call to fetchAuthenticated to ensure that initialisation has been completed
const getFetchWrapper = async () => {
if (!_fetchWrapper) {
_fetchWrapper = await initialiseFetchWrapper();
}
return _fetchWrapper;
};
I am using OAuth public client with authorozation code flow, refresh token and resource imdicators (RFC 8707).
The mentioned RFC adds the resource parameter to the authorization, token and refresh token requests. Unfortunatelly, it is not possible to pass the resource parameter to the token request and refresh token request.
The mentioned resource parameter can be a single string or an array of strings which brings additional issue. extraParams of type Record cannot hold multiple items with the same key. Moreover, serialization to query string using UrlSearchParams does not convert a Record with a property of type array os strings correctly
An example of such a Record is { resource: [ "r1", "r2"]} which should be serialized to "resource=r1&resource=r2".
How does this library store tokens? I am planning to use this in a browser setting (with authorization_code
) grant. Does this library store tokens automatically? I'm also trying to understand whether to store using HTTP-only cookies or localStorage
. How would a setup with HTTP-only cookies work with this library.
Thanks!
For openid-connect, the spec specifies that the endpoint should be /.well-known/openid-configuration
This conflicts with the currently default path, which seems to suit OAuth2 well though.
I suggest adding a flag to choose between OAuth2 only or OIDC.
This skips the first stage of oauth2 flow and just assumes that those tokens were obtained elsewhere.
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.