0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

docs: go web app integration guide (#1831)

This commit is contained in:
Xiao Yijun 2022-09-07 10:58:09 +08:00 committed by GitHub
parent ac945712e6
commit 9851619ba0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 764 additions and 1 deletions

View file

@ -0,0 +1,384 @@
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Alert from '@/components/Alert';
<Step
title="Add Logto SDK as a dependency"
index={0}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(1)}
>
<Alert>
The following demonstration is built upon the <a href="https://gin-gonic.com">Gin Web Framework</a>.
You may also integrate Logto into other frameworks by taking the same steps.
In the following code snippets, we assume your app is running on <code>http://localhost:8080</code>.
</Alert>
Run in the project root directory:
```bash
go get github.com/logto-io/go
```
Add the `github.com/logto-io/go/client` package to your application code:
```go
// main.go
package main
import (
"github.com/gin-gonic/gin"
// Add dependency
"github.com/logto-io/go/client"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello Logto!")
})
router.Run(":8080")
}
```
</Step>
<Step
title="Use sessions to store user authentication information"
subtitle="2 steps"
index={1}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(2)}
>
In traditional web applications, the user authentication information will be stored in the user session.
Logto SDK provides a `Storage` interface, you can implement a `Storage` adapter based on your web framework so that the Logto SDK can store user authentication information in the session.
<Alert>
We do NOT recommend using cookie-based sessions, as user authentication information stored by Logto may exceed the cookie size limit.
In this example, we use memory-based sessions. You can use Redis, MongoDB, and other technologies in production to store sessions as needed.
</Alert>
The `Storage` type in the Logto SDK is as follows:
```go
// github.com/logto-io/client/storage.go
package client
type Storage interface {
GetItem(key string) string
SetItem(key, value string)
}
```
We will use [github.com/gin-contrib/sessions](https://github.com/gin-contrib/sessions) as an example to demonstrate this process.
### Apply session middleware
Apply the [github.com/gin-contrib/sessions](https://github.com/gin-contrib/sessions) middleware to the application, so that we can get the user session by the user request context in the route handler:
```go
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-gonic/gin"
"github.com/logto-io/go/client"
)
func main() {
router := gin.Default()
// We use memory-based session in this example
store := memstore.NewStore([]byte("your session secret"))
router.Use(sessions.Sessions("logto-session", store))
router.GET("/", func(ctx *gin.Context) {
// Get user session
session := sessions.Default(ctx)
// ...
ctx.String(200, "Hello Logto!")
})
router.Run(":8080")
}
```
### Create session storage for Logto to store user authentication information
Create a `session_storage.go` file, define a `SessionStorage` and implement the Logto SDK's `Storage` interfaces:
```go
// session_storage.go
package main
import (
"github.com/gin-contrib/sessions"
)
type SessionStorage struct {
session sessions.Session
}
func (storage *SessionStorage) GetItem(key string) string {
value := storage.session.Get(key)
if value == nil {
return ""
}
return value.(string)
}
func (storage *SessionStorage) SetItem(key, value string) {
storage.session.Set(key, value)
storage.session.Save()
}
```
Now, in the route handler, you can create a session storage for Logto as follows:
```go
session := sessions.Default(ctx)
sessionStorage := &SessionStorage{session: session}
```
</Step>
<Step
title="Init LogtoClient"
subtitle="2 steps"
index={2}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(3)}
>
### Create LogtConfig
<pre>
<code className="language-go">
{`// main.go
func main() {
// ...
logtoConfig := &client.LogtoConfig{
Endpoint: "${props.endpoint}",
AppId: "${props.appId}",
AppSecret: "${props.appSecret}",
PersistAccessToken: true,
}
// ...
}`}
</code>
</pre>
The `PersistAccessToken` config indicates whether the access token needs to be stored in the session.
### Init LogtoClient for each user request
```go
// main.go
func main() {
// ...
router.GET("/", func(ctx *gin.Context) {
// Init LogtoClient
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// Use Logto to control the content of the home page
authState := "You are not logged in to this website. :("
if logtoClient.IsAuthenticated() {
authState = "You are logged in to this website! :)"
}
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>"
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// ...
}
```
</Step>
<Step
title="Sign in"
subtitle="3 steps"
index={3}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(4)}
>
### Configure Redirect URI
Add `http://localhost:8080/sign-in-callback` to the Redirect URI field.
This allows Logto to redirect the user to the `/sign-in-callback` route of your application after signing in.
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="application_details.redirect_uri" />
### Add a route for handling sign-in requests
```go
//main.go
func main() {
// ...
// Add a link to perform a sign-in request on the home page
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
// Add link
`<div><a href="/sign-in">Sign In</a></div>`
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// Add a route for handling sign-in requests
router.GET("/sign-in", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// The sign-in request is handled by Logto.
// The user will be redirected to the Redirect URI on signed in.
signInUri, err := logtoClient.SignIn("http://localhost:8080/sign-in-callback")
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// Redirect the user to the Logto sign-in page.
ctx.Redirect(http.StatusTemporaryRedirect, signInUri)
})
// ...
}
```
### Add a route for handling sign-in callback requests
When the user signs in successfully on the Logto sign-in page, Logto will redirect the user to the Redirect URI.
Since the Redirect URI is `http://localhost:8080/sign-in-callback`, we add the `/sign-in-callback` route to handle the callback after signing in.
```go
// main.go
func main() {
// ...
// Add a route for handling sign-in callback requests
router.GET("/sign-in-callback", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// The sign-in callback request is handled by Logto
err := logtoClient.HandleSignInCallback(ctx.Request)
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// Jump to the page specified by the developer.
// This example takes the user back to the home page.
ctx.Redirect(http.StatusTemporaryRedirect, "/")
})
// ...
}
```
</Step>
<Step
title="Sign out"
subtitle="2 steps"
index={4}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(5)}
>
### Configure Post Sign-out Redirect URI
Add `http://localhost:8080` to the Post Sign-out Redirect URI filed:
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutUris" title="application_details.post_sign_out_redirect_uri" />
This configuration enables the user to return to the home page after signing out.
### Add a route for handling signing out requests
```go
//main.go
func main() {
// ...
// Add a link to perform a sign-out request on the home page
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
`<div><a href="/sign-in">Sign In</a></div>` +
// Add link
`<div><a href="/sign-out">Sign Out</a></div>`
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// Add a route for handling signing out requests
router.GET("/sign-out", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// The sign-out request is handled by Logto.
// The user will be redirected to the Post Sign-out Redirect URI on signed out.
signOutUri, signOutErr := logtoClient.SignOut("http://localhost:8080")
if signOutErr != nil {
ctx.String(http.StatusOK, signOutErr.Error())
return
}
ctx.Redirect(http.StatusTemporaryRedirect, signOutUri)
})
// ...
}
```
After the user makes a signing-out request, Logto will clear all user authentication information in the session.
</Step>
<Step
title="Further readings"
subtitle="4 articles"
index={5}
activeIndex={props.activeStepIndex}
buttonText="general.done"
buttonType="primary"
onButtonClick={props.onComplete}
>
- [Customize sign-in experience](https://docs.logto.io/docs/recipes/customize-sie)
- [Enable SMS or email passcode sign-in](https://docs.logto.io/docs/tutorials/get-started/enable-passcode-sign-in)
- [Enable social sign-in](https://docs.logto.io/docs/tutorials/get-started/enable-social-sign-in)
- [Protect your API](https://docs.logto.io/docs/recipes/protect-your-api)
</Step>

View file

@ -0,0 +1,374 @@
import UriInputField from '@mdx/components/UriInputField';
import Step from '@mdx/components/Step';
import Alert from '@/components/Alert';
<Step
title="为项目安装 Logto SDK 依赖"
index={0}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(1)}
>
<Alert>
在本指南中,我们基于 <a href="https://gin-gonic.com">Gin Web 框架</a> 示范 SDK 的集成过程。你也可以采取同样的步骤轻松地将 Logto 集成到其他的 Web 框架中。
在示例代码中,我们假定你的应用运行在 <code>http://localhost:8080</code> 上。
</Alert>
在项目目录下执行:
```bash
go get github.com/logto-io/go
```
将 `github.com/logto-io/go/client` 包依赖添加到到代码中:
```go
// main.go
package main
import (
"github.com/gin-gonic/gin"
// 添加依赖
"github.com/logto-io/go/client"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello Logto!")
})
router.Run(":8080")
}
```
</Step>
<Step
title="使用 session 存储用户认证信息"
subtitle="共 2 步"
index={1}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(2)}
>
在传统网页应用中,用户的认证信息将会被存储在用户的 session 中。
Logto SDK 提供了一个 `Storage` 接口,你可以结合自己所使用的网络框架实现一个 `Storage` 的适配器,使 Logto SDK 能将用户认证信息存储到 session 中。
<Alert>
我们推荐使用非 cookie 的 session因为 Logto 所存储的信息可能会超过 cookie 的大小限制。在示例中我们使用基于内存 session在实际项目中你可以根据需要使用 Redis、 MongoDB 等技术来存储 session。
</Alert>
Logto SDK 中的 `Storage` 类型如下:
```go
// github.com/logto-io/client/storage.go
pacakge client
type Storage interface {
GetItem(key string) string
SetItem(key, value string)
}
```
我们将以 [github.com/gin-contrib/sessions](https://github.com/gin-contrib/sessions) 为例,示范这个过程。
### 在应用中使用 Session 中间件
在应用中使用 [github.com/gin-contrib/sessions](https://github.com/gin-contrib/sessions) 中间件,这样在请求的路由处理方法中就可以根据用户请求的上下文获取用户的 session
```go
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-gonic/gin"
"github.com/logto-io/go/client"
)
func main() {
router := gin.Default()
// 示例中使用基于内存的 session
store := memstore.NewStore([]byte("your session secret"))
router.Use(sessions.Sessions("logto-session", store))
router.GET("/", func(ctx *gin.Context) {
// 获取用户的 session
session := sessions.Default(ctx)
// ...
ctx.String(200, "Hello Logto!")
})
router.Run(":8080")
}
```
### 创建 session storage 供 Logto 存储用户认证信息
创建一个 `session_storage.go` 文件,定义一个 `SessionStorage` 并实现 Logto SDK 定义的 `Storage` 的接口:
```go
// session_storage.go
package main
import (
"github.com/gin-contrib/sessions"
)
type SessionStorage struct {
session sessions.Session
}
func (storage *SessionStorage) GetItem(key string) string {
value := storage.session.Get(key)
if value == nil {
return ""
}
return value.(string)
}
func (storage *SessionStorage) SetItem(key, value string) {
storage.session.Set(key, value)
storage.session.Save()
}
```
至此,你就可以在请求的路由处理方法中通过以下方式创建一个 session storage 给 Logto 使用了:
```go
session := sessions.Default(ctx)
sessionStorage := &SessionStorage{session: session}
```
</Step>
<Step
title="初始化 LogtoClient"
subtitle="共 2 步"
index={2}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(3)}
>
### 创建 Logto 配置
<pre>
<code className="language-go">
{`// main.go
func main() {
// ...
logtoConfig := &client.LogtoConfig{
Endpoint: "${props.endpoint}",
AppId: "${props.appId}",
AppSecret: "${props.appSecret}",
PersistAccessToken: true,
}
// ...
}`}
</code>
</pre>
其中 PersistAccessToken 表示是否需要把 access token 存储到 session 中。
### 为每个请求初始化 LogtoClient
```go
// main.go
func main() {
// ...
router.GET("/", func(ctx *gin.Context) {
// 初始化 LogtoClient
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// 使用 Logto 来控制首页的显示内容
authState := "You are not logged in to this website. :("
if logtoClient.IsAuthenticated() {
authState = "You are logged in to this website! :)"
}
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>"
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// ...
}
```
</Step>
<Step
title="登录"
subtitle="共 3 步"
index={3}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(4)}
>
### 配置 Redirect URI
将 `http://localhost:8080/sign-in-callback` 添加到 Redirect URI使用户登录 Logto 后能重定向到应用处理登录回调的 `/sign-in-callback` 路由:
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="application_details.redirect_uri" />
### 添加处理登录请求路由
```go
//main.go
func main() {
// ...
// 在 Home 页面添加登录请求的入口
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
// 添加登录请求的入口
`<div><a href="/sign-in">Sign In</a></div>`
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// 添加处理登录请求的路由
router.GET("/sign-in", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// 由 Logto 处理登录请求,指定登录成功后重定向到 Redirect URI
signInUri, err := logtoClient.SignIn("http://localhost:8080/sign-in-callback")
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// 将页面重定向到 Logto 登录页
ctx.Redirect(http.StatusTemporaryRedirect, signInUri)
})
// ...
}
```
### 添加处理登录回调路由
当我们在 Logto 登录页登录成功后Logto 会将用户重定向 Redirect URI。
因 Redirect URI 是 `http://localhost:8080/sign-in-callback`,所以我们添加 `/sign-in-callback` 路由来处理登录后的回调。
```go
// main.go
func main() {
// ...
// 添加处理登录回调的路由
router.GET("/sign-in-callback", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// 由 Logto 处理登录回调
err := logtoClient.HandleSignInCallback(ctx.Request)
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// 根据需求在登录成功后跳转到某个页面。(该示例中使用户回到首页)
ctx.Redirect(http.StatusTemporaryRedirect, "/")
})
// ...
}
```
</Step>
<Step
title="退出登录"
subtitle="共 2 步"
index={4}
activeIndex={props.activeStepIndex}
onButtonClick={() => props.onNext(5)}
>
### 配置 Post Sign-out Redirect URI
为应用添加 Post Sign-out Redirect URI使用户退出登录 Logto 之后将用户重定向回我们的应用。
将 `http://localhost:8080` 添加到 Post Sign-out Redirect URI使用户退出登录后回到应用首页
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutUris" title="application_details.post_sign_out_redirect_uri" />
### 添加退出登录请求路由
```go
//main.go
func main() {
// ...
// 在 Home 页面添加退出登录请求的入口
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
`<div><a href="/sign-in">Sign In</a></div>` +
// 添加退出登录请求的入口
`<div><a href="/sign-out">Sign Out</a></div>`
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// 添加处理退出登录请求的路由
router.GET("/sign-out", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// 由 Logto 处理退出登录请求,指定用户退出登录后重定向回 Post Sign-out Redirect URI
signOutUri, signOutErr := logtoClient.SignOut("http://localhost:8080")
if signOutErr != nil {
ctx.String(http.StatusOK, signOutErr.Error())
return
}
ctx.Redirect(http.StatusTemporaryRedirect, signOutUri)
})
// ...
}
```
当用户发起退出登录后Logto 会清除 session 中所有用户相关的认证信息。
</Step>
<Step
title="延展阅读"
subtitle="共 4 篇"
index={5}
activeIndex={props.activeStepIndex}
buttonText="general.done"
buttonType="primary"
onButtonClick={props.onComplete}
>
- [自定义登录体验](https://docs.logto.io/zh-cn/docs/recipes/customize-sie)
- [启用短信或邮件验证码登录](https://docs.logto.io/zh-cn/docs/tutorials/get-started/enable-passcode-sign-in)
- [启用社交登录](https://docs.logto.io/zh-cn/docs/tutorials/get-started/enable-social-sign-in)
- [保护你的 API](https://docs.logto.io/zh-cn/docs/recipes/protect-your-api)
</Step>

View file

@ -27,6 +27,7 @@ const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Elemen
vanilla: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vanilla.mdx')),
express: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/express.mdx')),
next: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/next.mdx')),
'go web': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/go-web.mdx')),
'ios_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/ios_zh-cn.mdx')),
'android_zh-cn': lazy(
async () => import('@/assets/docs/tutorial/integrate-sdk/android_zh-cn.mdx')
@ -40,6 +41,7 @@ const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Elemen
async () => import('@/assets/docs/tutorial/integrate-sdk/express_zh-cn.mdx')
),
'next_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/next_zh-cn.mdx')),
'go web_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/go-web_zh-cn.mdx')),
};
const Guide = ({ app, isCompact, onClose }: Props) => {

View file

@ -38,6 +38,8 @@ const getSampleProjectUrl = (sdk: SupportedSdk) => {
return `${githubUrlPrefix}/js/tree/master/packages/next-sample`;
case SupportedSdk.Express:
return `${githubUrlPrefix}/js/tree/master/packages/express-sample`;
case SupportedSdk.GoWeb:
return `${githubUrlPrefix}/go/tree/master/gin-sample`;
default:
return '';
}

View file

@ -14,10 +14,11 @@ export enum SupportedSdk {
Vanilla = 'Vanilla',
Express = 'Express',
Next = 'Next',
GoWeb = 'Go Web',
}
export const applicationTypeAndSdkTypeMappings = Object.freeze({
[ApplicationType.Native]: [SupportedSdk.iOS, SupportedSdk.Android],
[ApplicationType.SPA]: [SupportedSdk.React, SupportedSdk.Vue, SupportedSdk.Vanilla],
[ApplicationType.Traditional]: [SupportedSdk.Next, SupportedSdk.Express],
[ApplicationType.Traditional]: [SupportedSdk.Next, SupportedSdk.Express, SupportedSdk.GoWeb],
} as const);