No auth data in the request after exchange the Discord code

👋 I'm fowling the built in Oauth2 route documentation but, every time that I fire a request after authenticate I have a null value in the request.auth on the backend side. Here is my frontend code for exchange the Discord code
const API_URL = 'http://127.0.0.1:4000';

export const authorize = (code: string) => axios.post<Authorize>(
`${API_URL}/oauth/callback`,
JSON.stringify({ code }),
{ withCredentials: true }
);
const API_URL = 'http://127.0.0.1:4000';

export const authorize = (code: string) => axios.post<Authorize>(
`${API_URL}/oauth/callback`,
JSON.stringify({ code }),
{ withCredentials: true }
);
and here is the Precondition I'm using on the bot/backend side:
export const authenticated = () =>
createFunctionPrecondition(
(request: ApiRequest) => {
const authenticated = Boolean(request.auth?.token);
logDebug((authenticated) ? EventType.SUCCESS : EventType.ERROR, 'authenticated', request.auth);
return authenticated;
},
(_request: ApiRequest, response: ApiResponse) => response.error(HttpCodes.Unauthorized)
);
export const authenticated = () =>
createFunctionPrecondition(
(request: ApiRequest) => {
const authenticated = Boolean(request.auth?.token);
logDebug((authenticated) ? EventType.SUCCESS : EventType.ERROR, 'authenticated', request.auth);
return authenticated;
},
(_request: ApiRequest, response: ApiResponse) => response.error(HttpCodes.Unauthorized)
);
PS: The Discord code was success exchange and I received the LoginData back in the front end.
31 Replies
Favna
Favna•2y ago
You need to set cookies: 'include' in your frontend options. No idea if axios even supports that. Truth be told personally I recommend just using browser fetch. There is no reason to use XHR (what Axios wraps) in the current day and age unless you are uploading a file and you want to give an accurate progress to the user because XHR has event listeners.
Favna
Favna•2y ago
Looks like it might be withCredentials https://stackoverflow.com/a/43178070
Stack Overflow
Make Axios send cookies in its requests automatically
I am sending requests from the client to my Express.js server using Axios. I set a cookie on the client and I want to read that cookie from all Axios requests without adding them manually to requ...
Daniel Passos
Daniel PassosOP•2y ago
I tried the withCredentials using axios but that did not work. I will give it a try using fetch I changed it to fetch but still getting the same problem I also just tried what the skyra is doing but I still don't see any cookie in my browser. frontend Frontend URL: http://127.0.0.1:3000 apiFetch
const API_URL = 'http://127.0.0.1:4000';

export async function apiFetch<T>(path: string, options: RequestInit = {}) {
if (isInDevMode) {
await sleep(1000);
}

const response = await fetch(`${API_URL}${path}`, {
...options,
credentials: 'include',
headers: {
...options.headers,
'Content-Type': 'application/json'
}
});

const jsonResponse = await response.json();

if (jsonResponse.error) {
throw response;
} else {
return jsonResponse as T;
}
}
const API_URL = 'http://127.0.0.1:4000';

export async function apiFetch<T>(path: string, options: RequestInit = {}) {
if (isInDevMode) {
await sleep(1000);
}

const response = await fetch(`${API_URL}${path}`, {
...options,
credentials: 'include',
headers: {
...options.headers,
'Content-Type': 'application/json'
}
});

const jsonResponse = await response.json();

if (jsonResponse.error) {
throw response;
} else {
return jsonResponse as T;
}
}
Discord code exchange request
export function useAuthorize(code: string) {
const [user, setUser] = useState<User>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();

useEffect(() => {
setLoading(true);
apiFetch<Authorize>(`/oauth/callback`, {
method: FetchMethods.Post,
body: JSON.stringify({ code })
}).then(response => {
setUser(response.user);
console.log(response.user)
}).catch((err) => {
setError(err);
console.log(err);
}).finally(() => {
setLoading(false);
});
}, [code]);

return { user, loading, error };
}
export function useAuthorize(code: string) {
const [user, setUser] = useState<User>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();

useEffect(() => {
setLoading(true);
apiFetch<Authorize>(`/oauth/callback`, {
method: FetchMethods.Post,
body: JSON.stringify({ code })
}).then(response => {
setUser(response.user);
console.log(response.user)
}).catch((err) => {
setError(err);
console.log(err);
}).finally(() => {
setLoading(false);
});
}, [code]);

return { user, loading, error };
}
A Request after the token exchange
export function useFetchGuilds() {
const [guilds, setGuilds] = useState<Guild[]>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();

useEffect(() => {
setLoading(true);
apiFetch<Guild[]>('/guilds', {
method: FetchMethods.Get
}).then(guilds => {
setGuilds(guilds);
// TODO logger
console.log(guilds);
}).catch((err) => {
setError(err);
console.log(err);
}).finally(() => {
setLoading(false);
});
}, []);

return { guilds, loading, error };
}
export function useFetchGuilds() {
const [guilds, setGuilds] = useState<Guild[]>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();

useEffect(() => {
setLoading(true);
apiFetch<Guild[]>('/guilds', {
method: FetchMethods.Get
}).then(guilds => {
setGuilds(guilds);
// TODO logger
console.log(guilds);
}).catch((err) => {
setError(err);
console.log(err);
}).finally(() => {
setLoading(false);
});
}, []);

return { guilds, loading, error };
}
Backend/bot Bot URL: http://127.0.0.1:4000 .env
API_ORIGIN=http://127.0.0.1:3000
API_PORT=4000
API_PREFIX=
API_ORIGIN=http://127.0.0.1:3000
API_PORT=4000
API_PREFIX=
Authenticated precondition route
export const authenticated = () =>
createFunctionPrecondition(
(request: ApiRequest) => {
const authenticated = Boolean(request.auth?.token);
logDebug((authenticated) ? EventType.SUCCESS : EventType.ERROR, 'authenticated', request.auth);
return authenticated;
},
(_request: ApiRequest, response: ApiResponse) => response.error(HttpCodes.Unauthorized)
);
export const authenticated = () =>
createFunctionPrecondition(
(request: ApiRequest) => {
const authenticated = Boolean(request.auth?.token);
logDebug((authenticated) ? EventType.SUCCESS : EventType.ERROR, 'authenticated', request.auth);
return authenticated;
},
(_request: ApiRequest, response: ApiResponse) => response.error(HttpCodes.Unauthorized)
);
Guilds route
@ApplyOptions<RouteOptions>({ route: 'guilds' })
export class UserRoute extends Route {
@authenticated()
public async [methods.GET](request: ApiRequest, response: ApiResponse) {
const guilds = this.container.client.guilds.cache;
const flattenData = guilds.map(guild => flattenGuild(guild));
return response.json(flattenData);
}
}
@ApplyOptions<RouteOptions>({ route: 'guilds' })
export class UserRoute extends Route {
@authenticated()
public async [methods.GET](request: ApiRequest, response: ApiResponse) {
const guilds = this.container.client.guilds.cache;
const flattenData = guilds.map(guild => flattenGuild(guild));
return response.json(flattenData);
}
}
The backend response is including the access_token in the header response:
MORREA_AUTH=nFgNDjJ......==; Max-Age=604800; Domain=.0.1; Path=/; HttpOnly
MORREA_AUTH=nFgNDjJ......==; Max-Age=604800; Domain=.0.1; Path=/; HttpOnly
Favna
Favna•2y ago
im on pc now so I can check more thoroughly what are you (sapphire) client options looking like Daniel? Specifically .api (hide the secret values by just putting the env var names or '' or something)
Daniel Passos
Daniel PassosOP•2y ago
Thanks for looking into that @Favna but to be honest I don't think the problem is in the bot side, but here is my config anyway: SapphireClient
api: {
auth: {
id: envParseString('API_AUTH_ID'),
secret: envParseString('API_AUTH_SECRET'),
cookie: envParseString('API_AUTH_COOKIE'),
redirect: envParseString('API_AUTH_REDIRECT'),
scopes: [OAuth2Scopes.Identify]
},
prefix: envIsDefined('API_PREFIX') ? envParseString('API_PREFIX') : '',
origin: envParseString('API_ORIGIN'),
listenOptions: {
port: envParseNumber('API_PORT')
}
}
api: {
auth: {
id: envParseString('API_AUTH_ID'),
secret: envParseString('API_AUTH_SECRET'),
cookie: envParseString('API_AUTH_COOKIE'),
redirect: envParseString('API_AUTH_REDIRECT'),
scopes: [OAuth2Scopes.Identify]
},
prefix: envIsDefined('API_PREFIX') ? envParseString('API_PREFIX') : '',
origin: envParseString('API_ORIGIN'),
listenOptions: {
port: envParseNumber('API_PORT')
}
}
.env
API_AUTH_ID=942101011999363122
API_AUTH_SECRET=HIDDEN
API_AUTH_COOKIE=MORREA_AUTH
API_AUTH_REDIRECT=http://127.0.0.1:3000/oauth/authorize

API_ORIGIN=http://127.0.0.1:3000
API_PORT=4000
API_PREFIX=
API_AUTH_ID=942101011999363122
API_AUTH_SECRET=HIDDEN
API_AUTH_COOKIE=MORREA_AUTH
API_AUTH_REDIRECT=http://127.0.0.1:3000/oauth/authorize

API_ORIGIN=http://127.0.0.1:3000
API_PORT=4000
API_PREFIX=
Favna
Favna•2y ago
do you see the MORREA_AUTH token in your cookies? similar to this. And what browser do you use? I know for Skyra there at least used to be a problem with Firefox. I think Firefox resolved it (it was definitely on their side) but I'm not too sure. Also I think you need set domainOverwrite on the bot config api.auth.domainOverwrite to envParseString('OAUTH_DOMAIN_OVERWRITE') and in dev set it to 127.0.0.1
Daniel Passos
Daniel PassosOP•2y ago
No and that is "the problem" the front end is not "saving" the cookie, but it's receiving that information from the backend: MORREA_AUTH=nFgNDjJ......==; Max-Age=604800; Domain=.0.1; Path=/; HttpOnly Let me try the domainOverwrite
Favna
Favna•2y ago
lets see by default what we do is
const [splitHost] = this.request.headers.host?.split(':') ?? [''];

this.domain = domainOverwrite ?? this.getHostDomain(splitHost);
const [splitHost] = this.request.headers.host?.split(':') ?? [''];

this.domain = domainOverwrite ?? this.getHostDomain(splitHost);
this.request.headers.host should be 127.0.0.1:3000 after that... right so yeah that's definitely it we pass it into the psl library and read domain property but domain is 0.1 which you see in your backend as well (we return it as .${pslParsedInfo.domain})
Favna
Favna•2y ago
Favna
Favna•2y ago
I've been meaning to make @sapphire/psl for a long time because that lib is abandoned <_<
Daniel Passos
Daniel PassosOP•2y ago
I see!
Favna
Favna•2y ago
I guess when I do I should make a simple
if (host === '127.0.0.1') {
return host
}
if (host === '127.0.0.1') {
return host
}
kind of check
Daniel Passos
Daniel PassosOP•2y ago
So in theory that only happens because I'm using 127.0.0.1 , right?
Favna
Favna•2y ago
yes
Daniel Passos
Daniel PassosOP•2y ago
Gotcha!
Favna
Favna•2y ago
sadly however if you'd use localhost the cookie wouldnt be saved either because httponly cookies only work in a secure context or on that ip address it's part of the httponly cookies spec
Daniel Passos
Daniel PassosOP•2y ago
Nice catch! I will try the domainOverwrite and also create a fake domain in my /etc/hosts
Favna
Favna•2y ago
for production for @Skyra we set it to OAUTH_DOMAIN_OVERWRITE=.skyra.pw btw, the domain for @Skyra is https://skyra.pw
Skyra
Skyra Dashboard
Skyra is a multipurpose Discord bot designed to handle most tasks, helping users manage their servers easily.
Daniel Passos
Daniel PassosOP•2y ago
Cool! I will test it and let you know. Thanks for the help. I really appreciate that Just wondering if we should have this information on the documentation website. WDYT?
Favna
Favna•2y ago
adding this, waiting for it to compile now
Daniel Passos
Daniel PassosOP•2y ago
You fast, even not give me time to do that LOL
Favna
Favna•2y ago
tbf I was gonna write a message asking if you could maybe do it then I was like "ah hell it's probably faster if I do it" seeing as I'd otherwise have to explain about the submodules the website has and whatnot
Daniel Passos
Daniel PassosOP•2y ago
Np at all. I really appreciate the job you guys are doing on the framework. Congrats!
Favna
Favna•2y ago
for the record with that fake domain in your hosts file, it's pretty straightforward and should work in most cases I think but if you want to check how the domain will resolve beforehand just keep in mind we use the psl library.
import psl from 'psl';

psl.parse('mydomain.tld');
import psl from 'psl';

psl.parse('mydomain.tld');
Daniel Passos
Daniel PassosOP•2y ago
Just tested and domainOverwrite works like a charm I see the cookie on the front end now! Thanks for the help, I really appreciate that. Enjoy your weekend!
vladdy
vladdy•2y ago
Why are you using psl and not tldts
Favna
Favna•2y ago
1. Why are you using you when you have just as much responsibility so the proper address would be we 2. This is not a development channel. Please use the proper channel so these conversations do not get lost in the aether. I will answer this here once but I expect you to continue this conversation in #plugins-development 3. Well primarily because I didn't know it existed. That said, the tarball is also bloated because he published all the source .ts and the .tsbuildinfo files as well. The project is also very much showing it being outdated in that it still uses tsling and mocha.
vladdy
vladdy•2y ago
About the last part, ehh, as long as it stays up to date with tlds, its fine
Favna
Favna•2y ago
#plugins-development
vladdy
vladdy•2y ago
fair enough about the other 2 points
Want results from more Discord servers?
Add your server