1- # LinkedinStrategy
1+ # Remix Auth LinkedIn Strategy
22
3- The Linkedin strategy is used to authenticate users against a Linkedin account. It extends the [ OAuth2Strategy ] ( https://github.com/sergiodxa/remix-auth-oauth2 ) .
3+ The LinkedIn strategy is used for [ Remix Auth ] ( https://github.com/sergiodxa/remix-auth ) authentication using OAuth 2.0 and OpenID Connect protocols with LinkedIn .
44
55## Supported runtimes
66
@@ -9,105 +9,237 @@ The Linkedin strategy is used to authenticate users against a Linkedin account.
99| Node.js | ✅ |
1010| Cloudflare | ✅ |
1111
12+ ## Installation
13+
14+ ``` bash
15+ npm install remix-auth-linkedin
16+ ```
1217
1318## Usage
1419
1520### Create an OAuth application
1621
17- First you need to create a new application in the [ Linkedin's developers page] ( https://developer.linkedin.com/ ) . Then I encourage you to read [ this documentation page] ( https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2 ) , it explains how to configure your app and gives you useful information on the auth flow.
18- The app is mandatory in order to obtain a ` clientID ` and ` client secret ` to use with the Linkedin's API.
22+ First, create a new application in the [ LinkedIn Developers portal] ( https://developer.linkedin.com/ ) . Read the [ LinkedIn documentation] ( https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2 ) for details on configuring your app and understanding the authentication flow.
1923
2024### Create the strategy instance
2125
2226``` ts
23- // linkedin.server.ts
24- import { createCookieSessionStorage } from ' remix' ;
25- import { Authenticator } from ' remix-auth' ;
26- import { LinkedinStrategy } from " remix-auth-linkedin" ;
27-
28- // Personalize this options for your usage.
29- const cookieOptions = {
30- path: ' /' ,
31- httpOnly: true ,
32- sameSite: ' lax' as const ,
33- maxAge: 24 * 60 * 60 * 1000 * 30 ,
34- secrets: [' THISSHOULDBESECRET_AND_NOT_SHARED' ],
35- secure: process .env .NODE_ENV !== ' development' ,
36- };
37-
38- const sessionStorage = createCookieSessionStorage ({
39- cookie: cookieOptions ,
40- });
41-
42- export const authenticator = new Authenticator <string >(sessionStorage , {
43- throwOnError: true ,
44- });
45-
46- const linkedinStrategy = new LinkedinStrategy (
47- {
48- clientID: " YOUR_CLIENT_ID" ,
27+ import { Authenticator } from " remix-auth" ;
28+ import { LinkedInStrategy } from " remix-auth-linkedin" ;
29+
30+ // Create an instance of the authenticator
31+ const authenticator = new Authenticator <User >();
32+
33+ // Register the LinkedIn strategy
34+ authenticator .use (
35+ new LinkedInStrategy (
36+ {
37+ clientId: " YOUR_CLIENT_ID" ,
4938 clientSecret: " YOUR_CLIENT_SECRET" ,
50- // LinkedIn is expecting a full URL here, not a relative path
51- // see: https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?tabs=HTTPS1#step-1-configure-your-application
52- callbackURL: " https://example.com/auth/linkedin/callback" ;
53- },
54- async ({accessToken , refreshToken , extraParams , profile , context }) => {
55- /*
56- profile:
57- type LinkedinProfile = {
58- id: string;
59- displayName: string;
60- name: {
61- givenName: string;
62- familyName: string;
63- };
64- emails: Array<{ value: string }>;
65- photos: Array<{ value: string }>;
66- _json: LiteProfileData & EmailData;
67- } & OAuth2Profile;
68- */
69-
70- // Get the user data from your DB or API using the tokens and profile
71- return User .findOrCreate ({ email: profile .emails [0 ].value });
72- }
39+ // LinkedIn requires a full URL, not a relative path
40+ redirectURI: " https://example.com/auth/linkedin/callback" ,
41+ // Optional: customize scopes
42+ scopes: [" openid" , " profile" , " email" ],
43+ },
44+ async ({ profile , tokens , request }) => {
45+ // Find or create a user in your database
46+ return {
47+ id: profile .id ,
48+ email: profile .emails [0 ].value ,
49+ name: profile .displayName ,
50+ accessToken: tokens .accessToken (),
51+ refreshToken: tokens .refreshToken ? tokens .refreshToken () : null ,
52+ };
53+ }
54+ )
7355);
74-
75- authenticator .use (linkedinStrategy , ' linkedin' );
7656```
7757
7858### Setup your routes
7959
8060``` tsx
8161// app/routes/login.tsx
62+ import { Form } from " react-router" ;
63+
8264export default function Login() {
83- return (
84- <Form action = " /auth/linkedin" method = " post" >
85- <button >Login with Linkedin </button >
86- </Form >
87- )
65+ return (
66+ <Form action = " /auth/linkedin" method = " post" >
67+ <button >Login with LinkedIn </button >
68+ </Form >
69+ );
8870}
8971```
9072
9173``` tsx
9274// app/routes/auth/linkedin.tsx
93- import { ActionFunction , LoaderFunction } from ' remix'
94- import { authenticator } from ' ~/linkedin.server'
75+ import type { ActionFunctionArgs } from " react-router" ;
76+ import { authenticator } from " ~/services/auth.server" ;
77+
78+ export function loader() {
79+ return { message: " This route is not meant to be visited directly." };
80+ }
9581
96- export let loader: LoaderFunction = () => redirect (' /login' )
97- export let action: ActionFunction = ({ request }) => {
98- return authenticator .authenticate (' linkedin' , request )
82+ export async function action({ request }: ActionFunctionArgs ) {
83+ return await authenticator .authenticate (" linkedin" , request );
9984}
10085```
10186
87+ ### Handling the callback
88+
89+ The callback route handles the OAuth flow completion when LinkedIn redirects back to your application:
90+
10291``` tsx
10392// app/routes/auth/linkedin/callback.tsx
104- import { ActionFunction , LoaderFunction } from ' remix'
105- import { authenticator } from ' ~/linkedin.server'
106-
107- export let loader: LoaderFunction = ({ request }) => {
108- return authenticator .authenticate (' linkedin' , request , {
109- successRedirect: ' /dashboard' ,
110- failureRedirect: ' /login' ,
111- })
93+ import type { LoaderFunctionArgs } from " react-router" ;
94+ import { authenticator } from " ~/services/auth.server" ;
95+ import { redirect } from " react-router" ;
96+ import { sessionStorage } from " ~/services/session.server" ;
97+
98+ export async function loader({ request }: LoaderFunctionArgs ) {
99+ // Check for required parameters (optional but recommended)
100+ const url = new URL (request .url );
101+ const code = url .searchParams .get (' code' );
102+ const state = url .searchParams .get (' state' );
103+
104+ if (! code || ! state ) {
105+ return redirect (' /login?error=missing_params' );
106+ }
107+
108+ try {
109+ // Authenticate the user
110+ const user = await authenticator .authenticate (" linkedin" , request );
111+
112+ // Create session and store the authenticated user
113+ // Reference implementation: https://github.com/sergiodxa/sergiodxa.com/blob/main/app/routes/auth.%24provider.callback.ts
114+ const session = await sessionStorage .getSession (
115+ request .headers .get (" cookie" )
116+ );
117+
118+ session .set (" user" , user );
119+
120+ // Prepare headers for the response
121+ const headers = new Headers ();
122+
123+ // Add the session cookie to headers
124+ headers .append (
125+ " Set-Cookie" ,
126+ await sessionStorage .commitSession (session )
127+ );
128+
129+ // Optional: Add a cookie to clear the auth state
130+ // If you're using the stateless approach with commitSession, you don't need this
131+ // headers.append("Set-Cookie", await auth.clear(request));
132+
133+ // Redirect to dashboard with the session cookie
134+ return redirect (' /dashboard' , { headers });
135+ } catch (error ) {
136+ // Handle authentication errors
137+ console .error (' Authentication failed' , error );
138+ return redirect (' /login?error=auth_failed' );
139+ }
112140}
113141```
142+
143+
144+
145+ ## Configuration Options
146+
147+ ` LinkedInStrategy ` accepts the following configuration options:
148+
149+ ``` ts
150+ interface LinkedInStrategy .ConstructorOptions {
151+ /**
152+ * The client ID for your LinkedIn application
153+ */
154+ clientId: string ;
155+
156+ /**
157+ * The client secret for your LinkedIn application
158+ */
159+ clientSecret: string ;
160+
161+ /**
162+ * The URL LinkedIn will redirect to after authentication
163+ */
164+ redirectURI: URL | string ;
165+
166+ /**
167+ * The scopes to request from LinkedIn
168+ * @default ["openid", "profile", "email"]
169+ */
170+ scopes? : LinkedInScope [] | string ;
171+
172+ /**
173+ * The name and options for the cookie used to store state
174+ * @default " linkedin"
175+ */
176+ cookie? : string | (Omit <SetCookieInit , " value" > & { name: string });
177+ }
178+ ```
179+
180+ ## User Profile
181+
182+ After successful authentication, you'll receive user data in this format:
183+
184+ ``` ts
185+ interface LinkedInProfile {
186+ id: string ;
187+ displayName: string ;
188+ name: {
189+ givenName: string ;
190+ familyName: string ;
191+ };
192+ emails: Array <{ value: string }>;
193+ photos: Array <{ value: string }>;
194+ _json: {
195+ sub: string ;
196+ name: string ;
197+ given_name: string ;
198+ family_name: string ;
199+ picture: string ;
200+ locale: string ;
201+ email: string ;
202+ email_verified: boolean ;
203+ };
204+ }
205+ ```
206+
207+ ## Refresh Token
208+
209+ If you need to refresh an access token, you can use the ` refreshToken ` method:
210+
211+ ``` ts
212+ const strategy = new LinkedInStrategy (options , verify );
213+ const tokens = await strategy .refreshToken (refreshToken );
214+ ```
215+
216+ The most common approach is to store the refresh token in the user data and then update it after refreshing:
217+
218+ ``` ts
219+ authenticator .use (
220+ new LinkedInStrategy (
221+ options ,
222+ async ({ tokens , profile , request }) => {
223+ let user = await findOrCreateUser (profile );
224+ return {
225+ ... user ,
226+ accessToken: tokens .accessToken (),
227+ refreshToken: tokens .refreshToken ? tokens .refreshToken () : null ,
228+ };
229+ }
230+ )
231+ );
232+
233+ // Later in your code you can use it to get new tokens
234+ const tokens = await strategy .refreshToken (user .refreshToken );
235+ ```
236+
237+ ## Version Compatibility
238+
239+ - Version 3.x is compatible with Remix Auth v4.x
240+ - Version 2.x is compatible with Remix Auth v3.x
241+ - Version 1.x is compatible with Remix Auth v1.x and v2.x
242+
243+ ## License
244+
245+ MIT
0 commit comments