Closed xiaohanyu closed 6 months ago
After some evaluation we found that this is a bit hard to implement right.
Strapi (yes we use strapi in our backend) does not support one user to use multiple auth providers right now, check the issue and form.
There're some essential trickies to get it right here.
Let's say that we have a user registered via plain credentials, with username foo
and email foo@example.com
, then another user want to sign in via Google OAuth, with gmail account foo@gmail.com
, then it is highly likely that the system would lead to duplicated username of foo
, which would break lots of things.
Other CMS like [directus] seems have similar issue.
Let's postpone this issue and see whether strapi official could come up with better solution.
Another issue in strapi github: https://github.com/strapi/strapi/issues/8807
This is finally implemented and put online with the help of logto:
@xiaohanyu please how can this solution be implemented, i currently face this issue
@xiaohanyu please how can this solution be implemented, i currently face this issue
Hey, @Davidosky007 , do you use strapi as well? I decided to mgirate from strapi to payload in the end (for details, check my tweet here).
However the rationale is the same. Basically, many headless CMS have the same issue with OAuth, i.e, one email multiple account issue.
My solution here is to get rid of the internal, builtin auth of any CMS and then adopt a dedicated auth server, in my case, I chose logto, which provides a useful account linking feature that solves exactly one email multiple accounts issue.
For the story of introducing the new auth flow for PPResume, check the blog post.
Besides, I will list more tech details here, just FYI:
So basically, the architecture of current PPResume:
For example, I have the following definitions in payload for resumes:
export const Resumes: CollectionConfig = {
slug: 'resumes',
admin: {
useAsTitle: 'title',
defaultColumns: [
'id',
'title',
'ownerId',
'deleted',
'createdAt',
'updatedAt',
],
},
access: {
read: ({ req }) => {
return isAdmin(req) || isOwner(req)
},
create: ({ req }) => {
return isAdmin(req) || isCustomer(req)
},
update: ({ req }) => {
return isAdmin(req) || isOwner(req)
},
}
// ...
}
Here I have several auth check functions to different endpoints:
isAdmin
: whether the user is CMS admin, if yes, grant all accessisCustomer
: whether the user is a normal customer, if yes, he/she can create a new resumeisOwner
: whether the user is the owner of the resume, if yes, grant read access for one resumeA sample definition for isCustomer
:
/**
* Extracts the Bearer Token from the Authorization header.
*/
function extractBearerTokenFromHeaders({ authorization }: IncomingHttpHeaders) {
const bearerTokenIdentifier = 'Bearer'
if (!authorization) {
throw new Error('auth.authorization_header_missing')
}
if (!authorization.startsWith(bearerTokenIdentifier)) {
throw new Error('auth.authorization_token_type_not_supported')
}
return authorization.slice(bearerTokenIdentifier.length + 1)
}
/**
* Parse and verify the JWT token from the request.
*/
export async function verifyAuthFromRequest(
req: IncomingMessage
): Promise<JWTPayload> {
const bearerToken = extractBearerTokenFromHeaders(req.headers)
const { payload } = await jwtVerify(
bearerToken,
// generate a jwks using jwks_uri inquired from Logto server
createRemoteJWKSet(new URL(`${process.env.LOGTO_ENDPOINT}/oidc/jwks`)),
{
// expected issuer of the token, should be issued by the Logto server
issuer: `${process.env.LOGTO_ENDPOINT}/oidc`,
// expected audience token, should be the resource indicator of the current API
audience: process.env.PAYLOAD_RESUMES_RESOURCE,
}
)
return payload
}
/**
* Check whether the user is a valid customer.
*
* We only check whether the user has a valid JWT token issued by Logto server.
*
* @param req - The payload request object.
* @returns a valid JWT payload if the user is a valid customer.
*/
export async function isCustomer(req: PayloadRequest): Promise<JWTPayload> {
try {
const payload = await verifyAuthFromRequest(req)
return payload
} catch (err) {
console.error('Failed to verify auth from request, error: ', err)
}
}
For how to integrate logto with frontend and backend, you can check logto docs.
But still, there are many intricacies here. If you want to know more, I guess I can write some blog posts for this topic.
Description
As title, we want to support social sign in with Google
[Optional] Possible solutions
NA
Acceptance Criteria
Todo list