Merge pull request #11924 from backstage/orkohunter/sign-in-resolver

docs: Add missing sign in resolver step in getting started
This commit is contained in:
Himanshu Mishra
2022-06-21 16:48:19 +02:00
committed by GitHub
2 changed files with 96 additions and 64 deletions
+90 -61
View File
@@ -110,6 +110,7 @@ Now let's look at the example, with the rest of the commentary being made with i
the code comments:
```ts
// File: packages/backend/src/plugins/auth.ts
import {
createRouter,
providers,
@@ -170,11 +171,21 @@ property of each of the auth provider integrations. For example, the Google prov
a built in resolver that works just like the one we defined above:
```ts
providers.google.create({
signIn: {
resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(),
},
});
// File: packages/backend/src/plugins/auth.ts
export default async function createPlugin(
// ...
return await createRouter({
// ...
providerFactories: {
// ...
google: providers.google.create({
signIn: {
resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(),
},
});
}
})
)
```
There are also other options, like the this one that looks up a user
@@ -195,40 +206,49 @@ that happens during sign-in you can replace `ctx.signInWithCatalogUser` with a s
of lower-level calls:
```ts
// File: packages/backend/src/plugins/auth.ts
import { getDefaultOwnershipRefs } from '@backstage/plugin-auth-backend';
// This example only shows the resolver function itself.
async ({ profile: { email } }, ctx) => {
if (!email) {
throw new Error('User profile contained no email');
}
export default async function createPlugin(
// ...
return await createRouter({
// ...
providerFactories: {
// ...
google: async ({ profile: { email } }, ctx) => {
if (!email) {
throw new Error('User profile contained no email');
}
// This step calls the catalog to look up a user entity. You could for example
// replace it with a call to a different external system.
const { entity } = await ctx.findCatalogUser({
annotations: {
'acme.org/email': email,
},
});
// This step calls the catalog to look up a user entity. You could for example
// replace it with a call to a different external system.
const { entity } = await ctx.findCatalogUser({
annotations: {
'acme.org/email': email,
},
});
// In this step we extract the ownership references from the user entity using
// the standard logic. It uses a reference to the entity itself, as well as the
// target of each `memberOf` relation where the target is of the kind `Group`.
//
// If you replace the catalog lookup with something does not return
// an entity you will need to replace this step as well.
//
// You might also replace it if you for example want to filter out certain groups.
const ownershipRefs = getDefaultOwnershipRefs(entity);
// In this step we extract the ownership references from the user entity using
// the standard logic. It uses a reference to the entity itself, as well as the
// target of each `memberOf` relation where the target is of the kind `Group`.
//
// If you replace the catalog lookup with something that does not return
// an entity you will need to replace this step as well.
//
// You might also replace it if you for example want to filter out certain groups.
const ownershipRefs = getDefaultOwnershipRefs(entity);
// The last step is to issue the token, where we might provide more options in the future.
return ctx.issueToken({
claims: {
sub: stringifyEntityRef(entity),
ent: ownershipRefs,
},
});
};
// The last step is to issue the token, where we might provide more options in the future.
return ctx.issueToken({
claims: {
sub: stringifyEntityRef(entity),
ent: ownershipRefs,
},
});
};
}
})
)
```
## Sign-In without Users in the Catalog
@@ -252,38 +272,47 @@ check like in the example below, or you might for example look up the GitHub org
that the user belongs to using the user access token in the provided result object.
```ts
// File: packages/backend/src/plugins/auth.ts
import { DEFAULT_NAMESPACE, stringifyEntityRef, } from '@backstage/catalog-model';
// This example only shows the resolver function itself.
async ({ profile }, ctx) => {
if (!profile.email) {
throw new Error(
'Login failed, user profile does not contain an email',
);
}
// Split the email into the local part and the domain.
const [localPart, domain] = profile.email.split('@');
export default async function createPlugin(
// ...
return await createRouter({
// ...
providerFactories: {
// ...
google: async ({ profile }, ctx) => {
if (!profile.email) {
throw new Error(
'Login failed, user profile does not contain an email',
);
}
// Split the email into the local part and the domain.
const [localPart, domain] = profile.email.split('@');
// Next we verify the email domain. It is recommended to include this
// kind of check if you don't look up the user in an external service.
if (domain !== 'acme.org') {
throw new Error('Login failed, user email domain check failed');
}
// Next we verify the email domain. It is recommended to include this
// kind of check if you don't look up the user in an external service.
if (domain !== 'acme.org') {
throw new Error('Login failed, user email domain check failed');
}
// By using `stringifyEntityRef` we ensure that the reference is formatted correctly
const userEntityRef = stringifyEntityRef({
kind: 'User',
name: localPart,
namespace: DEFAULT_NAMESPACE,
});
// By using `stringifyEntityRef` we ensure that the reference is formatted correctly
const userEntityRef = stringifyEntityRef({
kind: 'User',
name: localPart,
namespace: DEFAULT_NAMESPACE,
});
return ctx.issueToken({
claims: {
sub: userEntityRef,
ent: [userEntityRef],
},
});
},
return ctx.issueToken({
claims: {
sub: userEntityRef,
ent: [userEntityRef],
},
});
},
}
})
)
```
## AuthHandler
+6 -3
View File
@@ -155,6 +155,8 @@ auth:
clientSecret: YOUR CLIENT SECRET
```
### Add sign-in option to the frontend
Backstage will re-read the configuration. If there's no errors, that's great! We
can continue with the last part of the configuration. The next step is needed to
change the sign-in page, this you actually need to add in the source code.
@@ -185,10 +187,11 @@ components: {
},
```
> Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an [explicit sign-in resolver](../auth/identity-resolver.md).
> Note: The default Backstage app comes with a guest Sign In Resolver. This resolver makes all users share a single "guest" identity and is only intended as a minimum requirement to quickly get up and running. You can read more about how [Sign In Resolvers](../auth/identity-resolver.md#sign-in-resolvers) play a role in creating a [Backstage User Identity](../auth/identity-resolver.md#backstage-user-identity) for logged in users.
That should be it. You can stop your Backstage App. When you start it again and
go to your Backstage portal in your browser, you should have your login prompt!
Restart Backstage from the terminal, by stopping it with `Control-C`, and starting it with `yarn dev` . You should be welcomed by a login prompt!
> Note: Sometimes the frontend starts before the backend resulting in errors on the sign in page. Wait for the backend to start and then reload Backstage to proceed.
To learn more about Authentication in Backstage, here are some docs you
could read: